Archives par mot-clé : Linux

Mon royaume pour 4 Go de VRAM : faire entrer Ollama dans le GPU

Dans un précédent billet, « Mon royaume pour une IA en localhost », je racontais mes premiers pas avec Ollama. L’enjeu était assez simple : si je dois confier des articles, des rapports ou des archives de recherche à une intelligence artificielle, je préfère que tout cela reste dans ma machine. Garder l’IA dans sa boîte, donc, avec l’espoir de conserver un peu de prise sur ce que je lui donne et sur ce qu’elle en fait.

À ce stade, faire répondre un modèle dans un terminal avait quelque chose de presque magique. Mais entre demander un résumé de trois pages et faire analyser un véritable corpus de recherche, il y a un petit gouffre. C’est dans ce gouffre que mon ordinateur et moi sommes tombés.

L’histoire qui suit est donc un retour d’expérience sur la manière dont j’ai essayé de faire travailler Ollama avec une petite carte graphique de portable. Ce n’est pas un tutoriel universel : les réglages dépendent du modèle, du GPU, de sa mémoire et de la taille des textes. C’est plutôt un pense-bête documenté pour éviter de refaire, dans six mois, le même chemin à travers les journaux d’erreurs.

Le contexte : 839 documents et une semaine de patience

Je travaille sur les archives du projet Santé & Territoires au Sénégal. Le corpus rassemble des comptes rendus, présentations, termes de référence, tableaux, documents de travail et rapports produits autour des Living Labs de Mbane et de Keur Momar Sarr, près du lac de Guiers.

L’objectif n’était pas seulement de faire résumer les documents. Je voulais reconstruire une chronologie, retrouver les groupes impliqués, repérer les formes de participation, distinguer ce qui avait été proposé de ce qui avait réellement été fait et suivre la manière dont la perspective « Une seule santé » avait été progressivement prise en compte. Le tout en conservant, pour chaque codage, un extrait et un chemin vers le document source.

Après extraction des textes, agrégation des doublons et exclusion des images, vidéos et fichiers audio, il restait tout de même 839 unités documentaires susceptibles de passer devant le LLM.

J’ai donc lancé mon script R, assez fier de moi, avec plusieurs workers pour les opérations locales et Ollama pour le codage. Quelques heures plus tard, la barre de progression affichait quelque chose comme :

Codage LLM documents   16/839 | écoulé 4h29m | ETA ≈ 231h22m

Le script annonçait ensuite, avec un calme que je trouvais presque provocateur :

Timeout ou échec pour D00010 chunk 4 (4000 caractères) ;
repli en 2 sous-chunk(s) de 2500 caractères au maximum.

Le premier bloc attendait le timeout. Puis le script le découpait en deux et recommençait. Quand plusieurs requêtes partaient en même temps, elles se disputaient la mémoire disponible. À ce rythme, l’analyse ne ressemblait plus vraiment à un gain de temps. Elle ressemblait plutôt à une manière sophistiquée de transformer un ordinateur portable en radiateur et par ces temps de canicules, pas la meilleur idée que j’ai eu!

Pas qu’un problème de modèle

Ma première réaction a été de chercher un modèle plus petit. C’est assez logique : un modèle de 8 milliards de paramètres sera plus lourd qu’un modèle de 3 ou 4 milliards. Mais cela n’expliquait pas complètement pourquoi des passages de quelques milliers de caractères pouvaient immobiliser Ollama pendant plusieurs minutes.

La machine possède deux processeurs graphiques :

Intel Corporation Meteor Lake-P [Intel Arc Graphics]
NVIDIA RTX 500 Ada Generation Laptop GPU

La carte NVIDIA ne dispose que de 4 Go de VRAM. Ce n’est pas énorme, mais un llama3.2:latest quantifié pèse environ 2 Go. Il devait donc être possible d’en faire quelque chose.

La commande suivante confirmait d’ailleurs qu’Ollama voyait bien la carte :

nvidia-smi

Dans la liste des processus, /usr/local/bin/ollama occupait environ 666 Mio de mémoire vidéo. Mais cette information ne disait pas quelle part du modèle était réellement exécutée sur le GPU. Pour cela, il fallait demander directement à Ollama :

ollama run llama3.2:latest "Réponds uniquement par OK"
ollama ps

Et là, surprise :

NAME               SIZE     PROCESSOR          CONTEXT
llama3.2:latest    18 GB    71%/29% CPU/GPU    16384

Le modèle téléchargé faisait environ 2 Go, mais Ollama annonçait une empreinte de 18 Go et ne plaçait que 29 % du calcul sur le GPU. Le même test avec qwen3:4b-instruct donnait encore moins :

qwen3:4b-instruct    18 GB    83%/17% CPU/GPU    16384

Le GPU était donc bien utilisé, mais comme une petite annexe du CPU. La majeure partie de l’inférence se faisait toujours dans la mémoire centrale. Hors, j’ai toujour entendu Théo me parler de GPU dans sa thèse.

Quatre contextes pour une seule petite carte

L’indice principal était la valeur CONTEXT 16384. Ollama avait réservé beaucoup plus de mémoire que nécessaire. Le parallélisme était ici particulièrement coûteux : plusieurs requêtes simultanées signifient plusieurs contextes à conserver en mémoire. Quatre contextes de 4 096 tokens donnent justement 16 384 tokens à allouer.

La documentation d’Ollama précise que la mémoire nécessaire augmente avec le produit :

OLLAMA_NUM_PARALLEL × OLLAMA_CONTEXT_LENGTH

Sur une grosse carte, plusieurs requêtes parallèles peuvent augmenter le débit. Sur 4 Go de VRAM, elles font surtout sortir le modèle du GPU. Le paradoxe est assez joli : j’avais demandé plus de parallélisme pour aller plus vite, et ce parallélisme était précisément ce qui ralentissait le calcul.

Il fallait donc distinguer deux choses dans mon script :

  • les opérations de lecture, d’extraction et de classement des fichiers, qui peuvent utiliser plusieurs cœurs CPU ;
  • les appels au modèle, qui doivent rester séquentiels tant que la mémoire du GPU est aussi contrainte.

Dans R, je pouvais conserver dix travailleurs pour préparer les documents, mais il fallait impérativement revenir à :

--llm-workers 1

Régler Ollama plutôt que brutaliser R

Ollama fonctionne comme un service. Le script R lui envoie des requêtes HTTP, mais c’est Ollama qui décide où charger le modèle, combien de contextes préparer et combien de temps le garder en mémoire. Il n’y avait donc pas besoin d’ajouter du code CUDA dans R. Il fallait régler le serveur.

Sous Linux, cela passe par une surcharge du service systemd :

sudo systemctl edit ollama.service

J’ai commencé avec cette configuration :

[Service]
Environment="OLLAMA_NUM_PARALLEL=1"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_CONTEXT_LENGTH=6144"
Environment="OLLAMA_KEEP_ALIVE=30m"

Puis il faut recharger la configuration et redémarrer Ollama :

sudo systemctl daemon-reload
sudo systemctl restart ollama

Les paramètres ont chacun un rôle assez simple :

  • OLLAMA_NUM_PARALLEL=1 interdit à un même modèle de préparer plusieurs requêtes en parallèle ;
  • OLLAMA_MAX_LOADED_MODELS=1 évite que plusieurs modèles se disputent les 4 Go de VRAM ;
  • OLLAMA_CONTEXT_LENGTH fixe la quantité maximale de texte et de réponse que le modèle peut garder dans son contexte ;
  • OLLAMA_KEEP_ALIVE=30m évite de recharger le modèle entre deux documents, sans pour autant l’immobiliser éternellement dans la mémoire.

Avec un contexte de 6 144 tokens, llama3.2:latest tenait entièrement sur le GPU. J’ai ensuite tenté 8 192 tokens, parce que mon prompt contient à la fois un codebook, un schéma JSON, des métadonnées et plusieurs passages du document.

Le résultat restait très honorable :

NAME               SIZE      PROCESSOR         CONTEXT
llama3.2:latest    3.5 GB    9%/91% CPU/GPU    8192

Le modèle était désormais exécuté à 91 % sur le GPU, contre 29 % avant le réglage. On était passé d’une empreinte annoncée de 18 Go à 3,5 Go.

Gratter encore un peu de mémoire

À 8 192 tokens, la carte était juste à la limite. Il restait une possibilité : réduire la mémoire occupée par le cache K/V, c’est-à-dire la mémoire dans laquelle le modèle conserve les éléments du contexte déjà traités.

J’ai donc ajouté :

Environment="OLLAMA_FLASH_ATTENTION=1"
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"

La configuration complète devient :

[Service]
Environment="OLLAMA_NUM_PARALLEL=1"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_KEEP_ALIVE=30m"
Environment="OLLAMA_FLASH_ATTENTION=1"
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"

Flash Attention permet de réduire la consommation de mémoire lorsque le contexte augmente. La quantification q8_0 utilise environ deux fois moins de mémoire que le cache f16 par défaut, avec une perte de précision généralement faible. Il existe également un cache q4_0, encore plus compact, mais la perte devient plus sensible. Pour un travail de codage scientifique, je préfère réduire légèrement le contexte plutôt que comprimer davantage le cache.

Il faut ici éviter une confusion : je n’ai pas fine-tuné le modèle au sens de l’apprentissage automatique. Je n’ai modifié ni ses poids ni son comportement appris. J’ai plutôt ajusté — tuné, si l’on veut — son régime d’inférence : nombre de requêtes, taille du contexte, occupation de la mémoire et répartition entre CPU et GPU.

Faire moins de requêtes reste la meilleure optimisation

Même correctement installé sur le GPU, un LLM n’a aucune raison de lire 839 documents de bout en bout si la majorité n’est pas utile à la question de recherche. Le réglage matériel ne devait donc pas remplacer le travail méthodologique.

J’ai repris le script R pour organiser l’analyse en deux passes.

La première réalise une présélection à rappel élevé. Elle lit le nom, le chemin et quelques passages de chaque document, puis décide s’il peut documenter les Living Labs, la participation des parties prenantes ou la perspective « Une seule santé ». Les décisions sont exportées dans un fichier CSV et peuvent être corrigées manuellement.

Rscript analyse_corpus_llama31_8b.R \
  --input "ST_KB_destination" \
  --output "resultat_corpus_llama31" \
  --model "llama3.2:latest" \
  --triage-model "llama3.2:latest" \
  --mode triage \
  --triage-batch-size 8 \
  --workers 10 \
  --llm-workers 1 \
  --api-timeout 300

Le fichier 00_triage_decisions.csv possède une colonne manual_selected. Je peux y inscrire TRUE pour repêcher un document ou FALSE pour l’écarter avant la seconde passe.

La seconde passe ne lit plus nécessairement toutes les pages. Elle présente au modèle le début, la fin et les passages les plus denses en termes liés au terrain et aux catégories analytiques. Chaque observation doit rester associée à un extrait et au chemin de sa source.

Rscript analyse_corpus_llama31_8b.R \
  --input "ST_KB_destination" \
  --output "resultat_corpus_llama31" \
  --model "llama3.2:latest" \
  --triage-model "llama3.2:latest" \
  --mode full \
  --deep-reading targeted \
  --workers 10 \
  --llm-workers 1 \
  --api-timeout 300

La machine travaille donc mieux, mais elle travaille surtout moins inutilement. C’est probablement l’optimisation la plus importante de toute cette histoire.

Ce que j’en retiens

Faire fonctionner une IA en local ne consiste pas seulement à télécharger un modèle et à lancer une commande. Entre le modèle et la machine s’installe tout un petit monde de paramètres : quantification, contexte, cache, parallélisme, chargement des modèles et partage entre CPU et GPU.

Les valeurs par défaut sont nécessairement génériques. Elles ne savent pas que mon ordinateur possède une RTX 500 Ada de seulement 4 Go, que je préfère une requête fiable à quatre requêtes concurrentes, ni que mon corpus contient des centaines de documents en français avec un schéma de codage assez lourd.

Il faut donc reprendre la main sur l’outil. Pas pour optimiser chaque seconde comme un nouvel ergonome de soi-même, mais pour que la technique reste proportionnée à la machine et à l’usage. Dans mon cas, la frugalité ne consistait pas seulement à choisir un petit modèle. Elle consistait à limiter le parallélisme, choisir la taille de contexte utile, écarter les documents hors sujet et maintenir une possibilité de validation humaine.

Le modèle est moins gros, les requêtes sont moins nombreuses et le GPU est enfin devenu autre chose qu’un petit témoin lumineux dans nvidia-smi. Ce n’est toujours pas le futur. Mais, cette fois, ça ressemble un peu plus à un outil que je peux habiter.

Et de la philo ?

Cette expérience prend alors un autre relief à la lecture de Günther Anders, mon héro. Le « rêve des machines » n’est pas celui de mieux servir les humains, mais celui d’un monde entièrement organisé selon leur propre fonctionnement, dans lequel l’homme finit par devenir un rouage de l’appareil qu’il a construit. Le décalage prométhéen apparaît lorsque notre capacité à produire de tels dispositifs dépasse notre capacité à nous les représenter, à les maîtriser et à décider collectivement de leurs usages.

À une échelle évidemment bien modeste, mon premier réflexe avait précisément consisté à adapter mon travail aux exigences d’Ollama : multiplier les traitements, découper les documents, attendre des centaines d’heures et laisser la machine imposer son rythme à l’enquête.

Paramétrer le modèle, limiter les requêtes et sélectionner les textes pertinents pourrait peut être revenir à inverser cette relation : faire entrer la machine dans les contraintes de la recherche, plutôt que transformer le chercheur — et son corpus — en simples rouages de la machine.

Quelques commandes à garder sous le coude

Identifier les cartes graphiques :

lspci | grep -Ei "VGA|3D|Display"

Surveiller la carte NVIDIA :

watch -n 1 nvidia-smi

Vérifier où Ollama a chargé le modèle :

ollama ps

Afficher la configuration transmise au service :

systemctl show ollama --property=Environment

Arrêter un modèle et libérer sa mémoire :

ollama stop llama3.2:latest

Quelques références

Mon royaume pour une IA en localhost

Depuis la sortie en 2022 de chatGPT, les solutions basées sur des IA sont partout. La question n’est plus, il me semble : « comment vivre sans ? », ou encore « est-ce qu’il est souhaitable de mettre de l’IA partout ?  » une question qui ne nous a jamais été posée collectivement.

Xavier De La Porte résume très bien mon état d’esprit dans l’intro de son podcast (le code a changer : Série « Humains-machines : nos langues entremêlées » Épisode 1/2 : Une histoire d’interruption ) : « je crois que je n’en peux déjà plus des discussions autour de ce que nous racontent les IA. Je n’en peux déjà plus d’entendre des gens s’extasier de tout ce que savent les IA, ou, a contrario, se plaindre de leurs hallucinations. Je n’en peux plus des gens qui me détaillent ce qu’ils font avec GPT, Claude, Perplexity ou Le Chat. »

L’IA est là, elle étend son emprise, est-ce que je peux limiter ma zone de contact en gardant le « contrôle » sur ce que je donne à la machine ? Parce que la tentation est de plus en plus forte de donner aux promptes des papiers scientifiques, des rapports, etc. pour lui demander des résumés… Bien sûr qu’il faut les avoir lus pour espérer échapper aux hallucinations, mais le temps que je pourrais gagner à lui faire faire cet élagage est sans commune mesure. Ce qui me pose évidemment la question de gagner du temps pour faire quoi ? Et bien, pour le moment c’est la théorie de la reine rouge. Comme tout le monde utilise chatGPT, pour aller plus vite, je suis toujours à la traine.

Bien sûr, céder à ce chantage, c’est faire un pas de plus dans le rêve des machines de Günther Anders ! Nous devenons toujours plus consommateurs :

« si nous sommes considérés comme de meilleurs travailleurs, notre consommation sera d’autant plus satisfaisante et nous pourrons accomplir plus rapidement le devoir qui nous incombe d’œuvrer comme liquidateur ; les produits apparaissent d’autant plus réussis qu’ils sont capables de se laisser rapidement user par nous. » (G. Anders, 2022, p.85)

Couverture du livre de Günther Anders "Le Rêve des machines" aux éditions allia

Donc comment garder le contrôle ? En gardant l’IA dans sa boite — c’est à dire dans ma machine. Rien ne fuite car tout reste là en local sur mon ordinateur.

Techniquement comment j’ai fait ?

Installer Ollama c’est facile

Ollama c’est facile a installer sur Linux :

curl -fsSL https://ollama.com/install.sh | sh

Cet utilitaire fonctionne un peu comme docker. On fournit un nom de modèle et il se charge de le télécharger et de le faire fonctionner. Une fois l’utilitaire installé, il faudra donc télécharger des LLM. Il y en a une flopé disponible sur le site. À titre d’exemple, voilà celui que j’essaie en ce moment.

ollama run gpt-oss

Normalement en sortie de commande (après le téléchargement de plusieurs Go de modèle), vous tomberez dans un prompt dans le terminal. Pour en sortir ctrl + d.

Installer open-WebUI c’est moins évident

Avant tout, j’ai perdu pas mal de temps à essayer de comprendre pourquoi les modèles que j’avais téléchargés avec Ollama n’étaient pas visibles dans l’interface de open-WebUI. Il se trouve qu’il y a encore des incompatibilités entre open-WebUI et Firefox. J’ai fini par trouver les infos !

Donc, pour installer le service web, la solution proposée est basée sur docker. Chez moi, c’est passé avec cette commande :

sudo docker run -d --network=host -v open-webui:/app/openWebUI -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main

Une fois que le docker est lancé, il met quelques minutes à s’instancier correctement. On peut accéder au service en local : http://localhost:8080/

RetEx à chaud 🪖

Après quelques essais, c’est chouette, mais ce n’est pas encore le futur! Déjà ça va pousser les « vieilles » machines dans la tombe et accentuer la fracture numérique. Ici une requête sur le modèle GPT-OSS occupe pas mal ma machine.

Je cherchais quelque chose qui me permette de faire de la synthèse de fichier. Le point positif c’est que c’est possible d’envoyer des fichiers au LLM dans l’interface d’open WebIU (qui intègre RAF), mais les formats ne sont pas tous supportés (PDF, odt, et autre format libre oui), mais les formats propriétaire comme docx semblent non pris en charge… Ensuite le niveau de réponse n’est pas foufou, et très dépendant du modèle qu’on charge. D’un point de vue contextuel, peut être que cela dépend des réglages de l’interface, mais la priorité n’est pas donnée au fichier que vous fournissez. Il arrive que se glissent des informations de fichier analysé précédemment.

Quelques références :

Peaufiner XFCE

Vous savez comme va la vie, on à parfois besoin de changer le papier peint ! Depuis quelques mois, mon Linux tombait en petites pièces. De petits dysfonctionnements … qui ne sont pas gênants. Et puis un jour de grisaille, on se dit que ça suffit ! Je suis reparti sur une install de manjaro (ce dériver d’Archlinux) avec XFCE.

Ça fonctionne bien, mais je cherchais quelque chose d’un peut plus eye candy... Et bien XCFE est super paramétrable ! Ici quelques idées de choses à installer pour s’éloigner de l’austérité 🙂

Plank un dock léger et minimal

Installer plank, ce chouette dock qui est disponible dans les dépôts officiels

sudo pacman -S plank

N’oubliez pas de l’ajouter dans les applications au démarrage

Il est possible qu’une ligne grise vienne faire comme un reflet à quelques centimètres au déçu du dock. C’est l’ombre … qu’il faudra désactiver dans les paramètres de fenêtres: Paramètres -> Peaufinage des fenêtres -> Compositeur.

Un thème pour changer de look

Il n’y a pas mal de thèmes et icônes disponible sur https://www.xfce-look.org/

Un peut d’espace pour openMole

openmoleSi vous voulez passer un de ces jours à openMole, voilà 2-3 astuces :
openMole va demander beaucoup de ressource à votre ordinateur (d’autant plus s’il tourne en local). Il peut donc s’avérer utilise de de pousser un peut les limites du système. ça se passe dans `/etc/security/limits.conf` vous pouvez coller à la fin du fichier de conf

*               hard    as              110000000
 *               hard    rss             100000000
 *               soft    nproc           20000
 *               hard    nofile          50000
 *               soft    nofile          50000

Les limites soft sont celles qui seront allouées par défaut à votre système et les limites hard sont celles que vous pourrez pousser à la main avec la commande ulimit…

Ensuite vous pourrez si le besoin se fait sentir également déverrouiller certaines limites avec :

ulimit -c unlimited

Voilà on est un peut moins à l’étroit pour jouer avec la mole!