<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Cyril Benhafed</title>
<link>https://blog.benhafed.com/articles/</link>
<atom:link href="https://blog.benhafed.com/articles/index.xml" rel="self" type="application/rss+xml"/>
<description>Retours d&#39;expérience, explorations techniques et mise en production — data science, MLOps, LLM.</description>
<generator>quarto-1.9.38</generator>
<lastBuildDate>Tue, 14 Apr 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Les LLM expliqués à mon petit frère</title>
  <dc:creator>Cyril Benhafed</dc:creator>
  <link>https://blog.benhafed.com/articles/predict-next-word/</link>
  <description><![CDATA[ 




<p><img src="https://blog.benhafed.com/articles/predict-next-word/cover.png" class="img-fluid"></p>
<section id="contexte" class="level1">
<h1>Contexte</h1>
<p>A l’occasion d’une discussion dans un bar, je me suis retrouvé à expliquer les fondamentaux de la construction des LLM à mon frère. Il faut dire que le sujet de l’IA est au coeur de toutes les conversations ces derniers temps : tout le monde utilise plus ou moins régulièrement ChatGPT (ou consort) et a son avis dessus. Mais la mécanique de ces modèles, le “comment ça marche”, reste nébuleux aux yeux des néophytes.</p>
<p>Les LLM sont construits sur un principe simple : prédire le prochain mot d’une phrase. Le modèle en lui même est bien sûr beaucoup plus compliqué, il faudrait parler de réseaux de neurones, de l’architecture transformers, etc. Mais la mécanique peut vraiment se réduire à ce simple principe de prédiction du prochain mot. C’est d’ailleurs pour ça que les LLM génèrent leur réponse de gauche à droite : ce n’est pas une cachoterie esthétique, c’est littéralement le modèle à l’oeuvre en pleine action.</p>
<p>Je me suis dit qu’il serait marrant - et didactique ! - de montrer à mon frère qu’il est possible de monter rapidement un modèle statistique tout bête qui ferait ce travail. Cela permet d’expliquer quelques concepts clés (la fenêtre de contexte, la température des modèles, etc.) à travers un modèle “jouet” (toy model). Et pour ajouter un peu de fun, j’ai récupéré les conversations Whatsapp de mon frère pour créer sa variante LLM !</p>
</section>
<section id="la-récupération-des-données" class="level1">
<h1>La récupération des données</h1>
<p>Je passerai rapidement sur la récupération des données Whatsapp, dans un esprit à l’opposé des bonnes pratiques Devops / Data science (faite à la main, non versionnée, etc.). L’idée est de monter un projet simple “one shot”, pas d’en faire un produit à mettre en production. Je suis donc allé sur l’application Android Whatsapp et j’ai exporté via l’option dédiée les conversations une à une. Cela donne des fichiers textes avec une ligne par message, qui sont facilement exploitables.</p>
<p>Par respect pour la vie privée de mon frère, j’ai caché son numéro de téléphone via une variable dans un fichier .env. C’est son numéro qui me permet d’identifier les messages qu’il a écrits.</p>
<div id="b7fc66e4" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> glob</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> re</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb1-6">load_dotenv()</span>
<span id="cb1-7"></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Je récupère le numéro de téléphone qui a été mis dans un env (qui est bien sur "git ignoré")</span></span>
<span id="cb1-9">target_number <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BROTHER_PHONE"</span>)</span>
<span id="cb1-10">escaped_number <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.escape(target_number)</span>
<span id="cb1-11">regex_pattern <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">rf"</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">^.</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*?</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>escaped_number<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">(</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">.</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On va stocker les messages dans une simple liste Python</span></span>
<span id="cb1-14">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-15"></span>
<span id="cb1-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On parcourt tous les fichiers de discussion, et on ajoute chaque ligne correspondant à mon frère (chaque "match") dans la liste</span></span>
<span id="cb1-17"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> glob.glob(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/raw/*.txt"</span>):</span>
<span id="cb1-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb1-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'r'</span>, encoding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'utf-8'</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb1-20">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> line <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> f:</span>
<span id="cb1-21">                line <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> line.strip()</span>
<span id="cb1-22">                match <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.search(regex_pattern, line)</span>
<span id="cb1-23">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> match:</span>
<span id="cb1-24">                    messages.append(match.group(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb1-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb1-26">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Erreur lors de la lecture de </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb1-27"></span>
<span id="cb1-28"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Nombre de messages trouvés : </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(messages)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Nombre de messages trouvés : 16164</code></pre>
</div>
</div>
<p>On voit que l’on récupère un bon nombre de messages : plus de 16.000 ! Mais soyons honnêtes, ça ne représente rien en traitement du langage naturel, surtout si on compare ça aux volumes de données titanesques utilisés pour l’entrainement des LLM actuels.</p>
</section>
<section id="le-prétraitement" class="level1">
<h1>Le prétraitement</h1>
<p>Maintenant que nous avons récupéré la donnée sous un format brut, nous allons appliquer plusieurs étapes de retraitement afin de la rendre facilement utilisable par un modèle statistique : nettoyage des données (enlever les smileys, etc.), normalisation et enfin tokenisation.</p>
<section id="nettoyage" class="level2">
<h2 class="anchored" data-anchor-id="nettoyage">Nettoyage</h2>
<p>Le nettoyage des données va être assez simple et rustique : on va garder uniquement les caractères standard du texte (ASCII pour les connaisseurs) : on transforme donc les “é” et “è” en “e” et ainsi de suite. On va aussi tout passer en minuscule. L’idée derrière ces 2 étapes est d’avoir le <strong>vocabulaire</strong>, c’est à dire le nombre de mot différent, le plus restreint possible. Comme on applique un modèle statistique basé sur la fréquence d’apparition des mots, on veut par exemple que “Ménage” et “menage” soient un seul et même mot (“menage” en l’occurrence). A noter qu’il y a des techniques avancées qui permettent d’aller plus loin dans la normalisation des mots, notamment le stemming et la lemmatization. Mais je ne les utiliserai pas ici pour rester simple.</p>
<p>Enfin un mot sur la ponctuation et les espaces. Toute ponctuation qui marque la fin d’une phrase, le “.” bien entendu, mais aussi “?”, “!”, etc. est convertie simplement en “.”. On les utilisera pour délimiter des phrases. Tous les espaces sont aussi standardisés : saut de ligne, espaces multiples et autres sont réduits à un simple espace ” “.</p>
<div id="9b92e4d1" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> re</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> unicodedata</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> clean_messages(messages):</span>
<span id="cb3-5">    cleaned_list <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb3-6">    </span>
<span id="cb3-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> msg <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> messages:</span>
<span id="cb3-8"></span>
<span id="cb3-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tous les caractères "spéciaux" sont transformés en ascii : on remplace les accents par leur équivalent ("é" -&gt; "e"), les cédilles ("ç" -&gt; "c"), etc.</span></span>
<span id="cb3-10">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> unicodedata.normalize(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'NFKD'</span>, msg)</span>
<span id="cb3-11">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> msg.encode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ascii'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ignore'</span>).decode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ascii'</span>)</span>
<span id="cb3-12"></span>
<span id="cb3-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On convertit tout en minuscule</span></span>
<span id="cb3-14">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> msg.lower()    </span>
<span id="cb3-15"></span>
<span id="cb3-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Toutes les ponctuations de fin de texte sont remplacées par un point "." (même chose pour les "..." remplacés par un ".")</span></span>
<span id="cb3-17">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.sub(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r'</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[!?;</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\.</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'.'</span>, msg)</span>
<span id="cb3-18">        </span>
<span id="cb3-19">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On supprime tout ce qui n'est pas du texte ou un point "." (remplacer par un espace)</span></span>
<span id="cb3-20">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.sub(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r'</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[^a-z</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\.\'</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">' '</span>, msg)</span>
<span id="cb3-21"></span>
<span id="cb3-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On nettoie le message système de Whatsapp qui indique qu'un média (photo, vidéo...) n'est pas récupéré</span></span>
<span id="cb3-23">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.sub(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r'medias omis'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">' '</span>, msg)</span>
<span id="cb3-24"></span>
<span id="cb3-25">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On nettoie les espaces en trop</span></span>
<span id="cb3-26">        msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> re.sub(<span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">r'</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">\s</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="vs" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">' '</span>, msg).strip()</span>
<span id="cb3-27"></span>
<span id="cb3-28">        cleaned_list.append(msg)</span>
<span id="cb3-29">        </span>
<span id="cb3-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> cleaned_list</span>
<span id="cb3-31"></span>
<span id="cb3-32">messages_clean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> clean_messages(messages)</span>
<span id="cb3-33"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(messages_clean[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>["honte a moi j'ai pas du tout eu le temps de m'en occuper.", "d'accord c'etait pour l'ultrabook il coute rien mais je sais pas si c'est ouf go de ram.", "ca a pas l'air dingue dingue en fait."]</code></pre>
</div>
</div>
</section>
<section id="tokenisation" class="level2">
<h2 class="anchored" data-anchor-id="tokenisation">Tokenisation</h2>
<p>Pour comprendre comment un LLM traite le langage, il faut s’intéresser à sa brique élémentaire : le token. C’est son unité basique de traitement du texte, ce qu’il va manipuler notamment pour prédire la suite de sa génération. Pour la plupart des LLM, ce token est une “partie de mot”. Par exemple pour le mot “antibrouillard”, il peut y avoir un token “anti” et un token “brouillard”. Cette décomposition d’un mot en blocs permet au LLM de “définir” un sens aux mots complexes (le “anti” lui permettant de “comprendre” que cela va contre le “brouillard”). A noter que tous les LLM n’utilisent pas la même tokenisation.</p>
<p>Dans notre cas d’exemple très simple, on va utiliser comme token un mot entier (donc “antibrouillard” pour reprendre notre exemple). Et pour faciliter encore plus cette étape, la logique de cet exercice étant avant tout pédagogique, on va séparer en mots uniquement en se basant sur les espaces qui viennent tout droit de notre étape de cleaning.</p>
<p>Détail important aussi, on l’a évoqué rapidement plus haut, on va délimiter des <strong>phrases</strong>, afin que notre LLM prédise des phrases qui se terminent. Sinon on se retrouverait à créer un générateur qui prédit des mots sans s’arrêter. Ce sont les “.” qui vont les délimiter. Par convention, on va pour chaque phrase ajouter 2 tokens techniques : <bos>, pour le début d’une phrase, <eos> pour la fin d’une phrase. On verra qu’ils nous serviront plus tard dans la prédiction du prochain mot (et/ou de la fin de la phrase !).</eos></bos></p>
<div id="d0801b8d" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">sentences_tokenized <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Notre tokenisation étant très simple (séparation par espace), on a uniquement à utiliser un strip pour séparer les mots. Un autre split est utilisé pour séparer les phrases avec les "."</span></span>
<span id="cb5-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> message <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> messages_clean:</span>
<span id="cb5-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> sentence <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [s.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> message.split(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> s.strip()]:</span>
<span id="cb5-6">        sentences_tokenized.append([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> sentence.split() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>])</span>
<span id="cb5-7"></span>
<span id="cb5-8"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(sentences_tokenized[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[['&lt;BOS&gt;', 'honte', 'a', 'moi', "j'ai", 'pas', 'du', 'tout', 'eu', 'le', 'temps', 'de', "m'en", 'occuper', '&lt;EOS&gt;'], ['&lt;BOS&gt;', "d'accord", "c'etait", 'pour', "l'ultrabook", 'il', 'coute', 'rien', 'mais', 'je', 'sais', 'pas', 'si', "c'est", 'ouf', 'go', 'de', 'ram', '&lt;EOS&gt;'], ['&lt;BOS&gt;', 'ca', 'a', 'pas', "l'air", 'dingue', 'dingue', 'en', 'fait', '&lt;EOS&gt;']]</code></pre>
</div>
</div>
</section>
<section id="wrap-up-prétraitement" class="level2">
<h2 class="anchored" data-anchor-id="wrap-up-prétraitement">Wrap up prétraitement</h2>
<p>Maintenant que nous avons fini avec les étapes de preprocessing, écrivons à partir de ce que l’on vient de faire une fonction utilitaire qui nous permettra d’appliquer ces traitements à du nouveau texte :</p>
<div id="278ec258" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> preprocess(messages):</span>
<span id="cb7-2">    cleaned_list <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> clean_messages(messages)</span>
<span id="cb7-3"></span>
<span id="cb7-4">    sentences <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb7-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> message <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cleaned_list:</span>
<span id="cb7-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> sentence <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [s.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> message.split(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"."</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> s.strip()]:</span>
<span id="cb7-7">            sentences.append([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> sentence.split() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>])</span>
<span id="cb7-8"></span>
<span id="cb7-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sentences</span></code></pre></div></div>
</div>
</section>
</section>
<section id="la-modélisation" class="level1">
<h1>La modélisation</h1>
<p>Maintenant que notre data est propre, sous format de phrases de tokens (représentées par des listes de listes en Python), nous avons tout ce qu’il faut pour compter quels sont les mots les plus fréquents après une séquence. Et ensuite pour prédire quel mot utiliser pour continuer une phrase.</p>
<section id="compter-les-occurrences" class="level2">
<h2 class="anchored" data-anchor-id="compter-les-occurrences">Compter les occurrences</h2>
<p>Pour alimenter notre modèle statistique, nous devons calculer à quelle fréquence un mot apparait après une suite de mots. On arrive déjà à une première définition intéressante : de combien de mots doit être notre <em>suite de mots</em> ?</p>
<p>Si on prend <strong>un</strong> seul mot, on ne risque pas de capturer beaucoup de sens. Il n’aura quasiment aucun <strong>contexte</strong> sur la phrase. Par exemple il prédira peut-être le mot “balle” après “la”, alors que la phrase commence par “il a mangé la”. Pas idéal pour prédire des phrases qui ont un minimum de sens.</p>
<p>Si on choisit un contexte <strong>long</strong> - de 10 mots par exemple - on fait face à 2 contraintes. Déjà le modèle va être très <strong>lourd</strong> à calculer. Mais surtout, vu la taille du jeu de données d’entrainement qu’on lui donne, qui est assez réduite, le modèle risque d’avoir trop peu d’exemples pour une suite de mots et peut donc se retrouver à “<strong>overfitter</strong>”. L’overfit est un concept clé dans le machine learning, c’est le manque de capacité d’un modèle à généraliser. Reprenons un exemple, si la suite de mots est “il a mangé à 4h et maintenant il va se” et que l’on a un seul exemple dans notre jeu de données (mon frère qui m’aurait dit par exemple : “il a mangé à 4h et maintenant il va se <em>coucher</em>”). Le mot prédit sera <strong>toujours</strong> “coucher” après cette suite de mots.</p>
<p>Une longue fenêtre de contexte implique donc qu’il faut un grand ensemble de données pour avoir des résultats pertinents. Pour information, les LLM actuels ont des fenêtres de contexte qui peuvent monter à 1 million de tokens, voire plus. Un million de tokens, c’est à peu près la taille de l’intégrale du Seigneur des Anneaux, il y a de quoi faire donc. Mais les moyens, en termes de données d’entrainement, et de puissance de calcul, sont bien sûr d’un autre ordre que notre exemple.</p>
<p>Nous allons de notre côté nous arrêter sur une fenêtre de <strong>3 mots</strong>. On va donc calculer les occurrences de mots après une séquence de jusqu’à 3 mots. L’implémentation est un peu tricky : notre fenêtre doit aller <strong>jusqu’à</strong> 3 mots, mais elle peut être plus petite. Par exemple, pour un début de phrase, nous aurons moins de 3 mots de contexte pour deviner le prochain.</p>
<p>Une fois les occurrences comptabilisées, on va appliquer une étape de normalisation pour avoir des probabilités.</p>
<div id="bd884021" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> defaultdict, Counter</span>
<span id="cb8-2"></span>
<span id="cb8-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># La taille MAX de la fenêtre (le n-gramme cible)</span></span>
<span id="cb8-4">MAX_WINDOW <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb8-5"></span>
<span id="cb8-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_flexible_ngram_model(sentences_tokenized, max_window):</span>
<span id="cb8-7">    counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> defaultdict(Counter)</span>
<span id="cb8-8"></span>
<span id="cb8-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On parcourt chaque phrase avec une fenêtre flexible</span></span>
<span id="cb8-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> sentence <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sentences_tokenized:</span>
<span id="cb8-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pour chaque position i dans la phrase...</span></span>
<span id="cb8-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(sentence)):</span>
<span id="cb8-13">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On définit la fin de notre fenêtre glissante</span></span>
<span id="cb8-14">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Elle ne doit pas dépasser max_window et ne pas sortir de la phrase actuelle</span></span>
<span id="cb8-15">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On cherche le mot suivant, donc on regarde jusqu'à i + max_window</span></span>
<span id="cb8-16">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> window_size <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, max_window <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb8-17">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Le "contexte" est la séquence de mots précédant le mot cible</span></span>
<span id="cb8-18">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On définit l'index du mot cible (le dernier de notre fenêtre)</span></span>
<span id="cb8-19">                target_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> window_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-20">                </span>
<span id="cb8-21">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On vérifie qu'on ne dépasse pas la fin de la phrase actuelle</span></span>
<span id="cb8-22">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> target_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(sentence):</span>
<span id="cb8-23">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Le contexte est ce qui précède target_idx, mais sans dépasser max_window-1</span></span>
<span id="cb8-24">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On prend les mots de l'index (i) jusqu'à (target_index - 1)</span></span>
<span id="cb8-25">                    context_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i</span>
<span id="cb8-26">                    </span>
<span id="cb8-27">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Si le contexte est vide (cas du premier mot de la phrase), </span></span>
<span id="cb8-28">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># on peut décider soit de l'ignorer, soit de marquer un contexte spécial.</span></span>
<span id="cb8-29">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Ici, pour la pédagogie, on ne prend que si context_start &lt; target_idx</span></span>
<span id="cb8-30">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> context_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> target_idx:</span>
<span id="cb8-31">                        context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(sentence[context_start:target_idx])</span>
<span id="cb8-32">                        next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sentence[target_idx]</span>
<span id="cb8-33">                        counts[context][next_word] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-34"></span>
<span id="cb8-35">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Normalisation</span></span>
<span id="cb8-36">    model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb8-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> context, next_words_counts <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.items():</span>
<span id="cb8-38">        total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(next_words_counts.values())</span>
<span id="cb8-39">        model[context] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {word: count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> total <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> word, count <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> next_words_counts.items()}</span>
<span id="cb8-40">        </span>
<span id="cb8-41">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model</span>
<span id="cb8-42"></span>
<span id="cb8-43">bro_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_flexible_ngram_model(sentences_tokenized, MAX_WINDOW)</span></code></pre></div></div>
</div>
<p>Jetons un coup d’oeil rapide au dictionnaire ainsi créé :</p>
<div id="81f7363b" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">bro_model[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"il"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"a"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"un"</span>, )]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="6">
<pre><code>{'vieux': 0.3333333333333333,
 'diplome': 0.3333333333333333,
 'petit': 0.3333333333333333}</code></pre>
</div>
</div>
</section>
<section id="la-prédiction" class="level2">
<h2 class="anchored" data-anchor-id="la-prédiction">La prédiction</h2>
<p>La prédiction du prochain mot est plus subtile qu’on peut le penser au premier abord. Il ne s’agit pas de “bêtement” prédire le mot le plus <strong>fréquent</strong> après une séquence de mots. Cela donnerait un LLM assez … plat et peu créatif. Construisons un exemple rapide pour s’en rendre compte.</p>
<div id="4c85f595" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> predict_next_word(text):</span>
<span id="cb11-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pour construire les tokens sur lesquels on va prédire notre prochain mot, on utilise la fonction preprocess qu'on a construite plus haut. En ne prenant en compte que la dernière phrase</span></span>
<span id="cb11-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Si le texte est vide (ou ne contient aucun mot), preprocess ne renvoie aucune phrase : on part alors directement de &lt;BOS&gt; pour générer une phrase de bout en bout</span></span>
<span id="cb11-4">    sentences <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> preprocess([text])</span>
<span id="cb11-5">    tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sentences[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> sentences <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>]</span>
<span id="cb11-6"></span>
<span id="cb11-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tant que l'on arrive pas à la fin de phrase, on répète la prédiction du prochain mot</span></span>
<span id="cb11-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> tokens[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>:</span>
<span id="cb11-9">        next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb11-10"></span>
<span id="cb11-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On utilise une boucle pour gérer les séquence qu'on ne connaitrait pas (qui ne serait jamais apparues dans notre jeu d'entrainement). Si la séquence en cours n'a jamais été vue, alors on la réduit (on enlève des mots) pour augmenter nos chance de récupérer des probas</span></span>
<span id="cb11-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> window_size <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(MAX_WINDOW <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(tokens)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb11-13">            context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(tokens[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>window_size:])</span>
<span id="cb11-14">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> context <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> bro_model:</span>
<span id="cb11-15">                next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(bro_model[context], key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>bro_model[context].get)</span>
<span id="cb11-16">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb11-17">        </span>
<span id="cb11-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Si même en réduisant le contexte, on tombe sur un mot qui n'a jamais été vu, alors on prend le mot le plus fréquemment cité en début de phrase (callback assez rustique on en conviendra...)</span></span>
<span id="cb11-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> next_word <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb11-20">            next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(bro_model[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>,)], key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>bro_model[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>,)].get)</span>
<span id="cb11-21"></span>
<span id="cb11-22">        tokens.append(next_word)</span>
<span id="cb11-23"></span>
<span id="cb11-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span>.join(t <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tokens <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>))</span></code></pre></div></div>
</div>
<p>Faisons maintenant quelques tests :</p>
<div id="e02cb319" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> textwrap</span>
<span id="cb12-2"></span>
<span id="cb12-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fonction utilitaire dont le but est d'afficher l'input et l'output du modèle</span></span>
<span id="cb12-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> print_results(pairs):</span>
<span id="cb12-5">    blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb12-6"></span>
<span id="cb12-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> input_text, result <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> pairs:</span>
<span id="cb12-8">        input_block <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> textwrap.fill(input_text, initial_indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Input : "</span>, subsequent_indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb12-9">        output_block <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> textwrap.fill(result, initial_indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Output: "</span>, subsequent_indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb12-10">        blocks.append(input_block <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> output_block)</span>
<span id="cb12-11"></span>
<span id="cb12-12">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(blocks))</span>
<span id="cb12-13"></span>
<span id="cb12-14">print_results([</span>
<span id="cb12-15">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>, predict_next_word(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>)),</span>
<span id="cb12-16">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle est"</span>, predict_next_word(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle est"</span>)),</span>
<span id="cb12-17">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle est trop"</span>, predict_next_word(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle est trop"</span>)),</span>
<span id="cb12-18">])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Input : elle
Output: elle est trop belle

Input : elle est
Output: elle est trop belle

Input : elle est trop
Output: elle est trop belle</code></pre>
</div>
</div>
<p>On conviendra qu’on a déjà vu plus créatif comme LLM.</p>
<p>C’est que les LLM sont construits sur une approche <strong>probabiliste</strong>, et non déterministique comme le modèle que l’on vient de créer. Cela leur permet d’avoir un ton plus naturel, plus humain, mais aussi d’être plus “créatif”. L’inconvénient évident, c’est que pour un même prompt, un LLM retournera des réponses différentes. Pire, c’est ce qui peut générer les fameuses <strong>hallucinations</strong> dont on a beaucoup parlé : le LLM ne répète pas méthodiquement ce sur quoi il a été entrainé, il peut dévier et générer des incohérences liées à sa nature aléatoire.</p>
<p>La plupart des LLM permettent de contrôler cette partie “stochastique” - mot compliqué pour dire basé sur une nature aléatoire - via leur API à travers deux paramètres :</p>
<ul>
<li>la température, qui permet d’influer sur la probabilité de choisir un mot moins fréquent</li>
<li>le “top k”, qui permet de déterminer quels sont les k mots les plus fréquents sur lesquels le LLM pourra piocher.</li>
</ul>
<p>Encore une fois, pour rester simple, on va se concentrer sur un seul concept, le top k. L’idée derrière ce paramètre est de limiter le choix du modèle aux k mots les plus fréquents pour sa prédiction. Par exemple les 5 mots les plus fréquents. Cela évite au modèle de possiblement prédire un mot très rare après une séquence, qui pourrait l’amener à une phrase sans queue ni tête.</p>
<p>Mettons en place ce top k dans notre fonction, en prenant le <strong>top 5</strong>.</p>
<div id="70fe887d" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> random</span>
<span id="cb14-2"></span>
<span id="cb14-3">random.seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb14-4"></span>
<span id="cb14-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> predict_next_word_top_k(text, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>):</span>
<span id="cb14-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On preprocess le texte en utilisant la fonction que l'on avait définie</span></span>
<span id="cb14-7">    sentences <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> preprocess([text])</span>
<span id="cb14-8">    tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sentences[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> sentences <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>]</span>
<span id="cb14-9"></span>
<span id="cb14-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fonction qui permet de normaliser les probabilités récupérées des top k mots les plus fréquents</span></span>
<span id="cb14-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sample_top_k(distribution, k):</span>
<span id="cb14-12">        top_words <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(distribution.items(), key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: x[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)[:k]</span>
<span id="cb14-13">        words, probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>top_words)</span>
<span id="cb14-14">        total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(probs)</span>
<span id="cb14-15">        renormalized_probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> total <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> probs]</span>
<span id="cb14-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> random.choices(words, weights<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>renormalized_probs, k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb14-17"></span>
<span id="cb14-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tant que l'on arrive pas sur le token &lt;EOS&gt;, on continue de générer des mots</span></span>
<span id="cb14-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> tokens[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>:</span>
<span id="cb14-20">        next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb14-21"></span>
<span id="cb14-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># On utilise la fenêtre de contexte (window_size) : si on ne trouve pas d'entrée dans le modèle, on raccourcit celle-ci en retirant un mot</span></span>
<span id="cb14-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> window_size <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(MAX_WINDOW <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(tokens)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb14-24">            context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>(tokens[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>window_size:])</span>
<span id="cb14-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> context <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> bro_model:</span>
<span id="cb14-26">                next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_top_k(bro_model[context], k)</span>
<span id="cb14-27">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb14-28">        </span>
<span id="cb14-29">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Si malgré la réduction de la fenêtre de contexte on ne trouve toujours pas d'entrée dans le modèle, alors on prend le mot le plus fréquent utilisé en début de phrase (après &lt;BOS&gt;)</span></span>
<span id="cb14-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> next_word <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb14-31">            next_word <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_top_k(bro_model[(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>,)], k)</span>
<span id="cb14-32"></span>
<span id="cb14-33">        tokens.append(next_word)</span>
<span id="cb14-34"></span>
<span id="cb14-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span>.join(t <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tokens <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;BOS&gt;"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;EOS&gt;"</span>))</span>
<span id="cb14-36"></span>
<span id="cb14-37">print_results([</span>
<span id="cb14-38">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>)),</span>
<span id="cb14-39">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>)),</span>
<span id="cb14-40">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"elle"</span>)),</span>
<span id="cb14-41">])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Input : elle
Output: elle etait tres contente

Input : elle
Output: elle a l'air bien cool

Input : elle
Output: elle a toujours un qui vomi apres</code></pre>
</div>
</div>
<p>On voit que les sorties sont beaucoup plus créatives et moins fermées que notre modèle déterministique. Les phrases ont l’air naturelles, même si elles n’ont pas beaucoup de sens (mention spéciale pour la 3e, particulièrement longue).</p>
<p>Essayons quelques exemples de phrases créées de bout en bout :</p>
<div id="b1fb9952" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">random.seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb16-2">print_results([</span>
<span id="cb16-3">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)),</span>
<span id="cb16-4">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)),</span>
<span id="cb16-5">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, predict_next_word_top_k(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)),</span>
<span id="cb16-6">])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>
Output: ca a l'air trop bien


Output: ca va mon bro en tout cas on est trop soulage c'est alle super
        vite


Output: trop fort</code></pre>
</div>
</div>
<p>Encore une fois, on retrouve bien le ton court et naturel des conversations de messagerie instantanée.</p>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>En l’espace de quelques lignes de code, on a construit un petit modèle statistique de langage qui permet d’imiter - grossièrement - le fonctionnement des LLM. On a vu que cela permettait de générer des phrases “réalistes” pour le contexte (messagerie instantanée). Mais on a vu aussi clairement ses limites, avec des phrases générées qui n’avaient parfois pas grand sens.</p>
<p>Cela nous a permis surtout au fil du chemin de passer en revue des concepts clés du domaine :</p>
<ul>
<li>la <strong>tokenisation</strong> qui permet de découper un texte en unités manipulables par un modèle</li>
<li>la <strong>context window</strong> (fenêtre de contexte), qui détermine jusqu’où “voit” le modèle</li>
<li>on a vu aussi le concept d’<strong>overfitting</strong> appliqué aux LLM. Je reviendrai dessus très prochainement dans un autre article</li>
<li>enfin, et s’il y a un point sur lequel j’aimerais bien insister c’est celui-là, on a vu la nature <strong>probabiliste</strong> des LLM, qui est autant un atout (phrases naturelles, créatives…) qu’un risque à suivre de près</li>
</ul>
<p>Et qu’est ce qu’en a pensé mon frère au final ? L’exercice l’a bien amusé, mais j’ai l’impression qu’il essaie de mettre à mal mon modèle en me répondant uniquement par émoji maintenant. Peut être un LLM émoji à mettre en place pour le contrer ?</p>


</section>

 ]]></description>
  <category>genai</category>
  <guid>https://blog.benhafed.com/articles/predict-next-word/</guid>
  <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://blog.benhafed.com/articles/predict-next-word/cover.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Tests de modèles de génération d’images en local</title>
  <dc:creator>Cyril Benhafed</dc:creator>
  <link>https://blog.benhafed.com/articles/blog-visual_generation/</link>
  <description><![CDATA[ 




<p><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sana_sprint_0.png" class="img-fluid"></p>
<section id="contexte" class="level1">
<h1>Contexte</h1>
<section id="a-la-recherche-dune-identité-visuelle" class="level2">
<h2 class="anchored" data-anchor-id="a-la-recherche-dune-identité-visuelle">A la recherche d’une identité visuelle</h2>
<p>Alors que j’écrivais le premier billet de ce blog, je me suis dit qu’il me fallait des images pour illustrer le contenu et rendre la lecture plus agréable. Comme nous sommes à l’ère de l’IA générative, plus besoin de passer des heures sur <a href="https://pixabay.com/">Pixabay</a> (ou autre site d’images libres de droit) à la recherche de l’image parfaite : ces nouveaux modèles permettent de créer des images photoréalistes sur commande (ou plutôt sur “prompt”). Dans mon cas, ce qui m’intéressait particulièrement était de pouvoir créer une <em>identité visuelle</em>, en gardant une constance / cohérence dans les illustrations de mon blog.</p>
<p>Pour changer des éternelles images sur l’IA type science fiction avec des faisceaux de lumières sur fond noir, j’ai voulu prendre le contre pied et partir sur un thème léger et original. Je suis un grand fan (et collectionneur) de comics strips : ces petites histoires comiques qui se développent sur quelques cases dans les journaux papiers, comme Calvin &amp; Hobbes, Snoopy, Pearls Before Swine… Mon idée est donc d’apporter ce type de visuel très croquis / cartoon pour ce blog. En le pilotant donc à l’aide des prompts que j’utiliserai pour développer ces images.</p>
</section>
<section id="létat-de-lart-à-lheure-daujourdhui" class="level2">
<h2 class="anchored" data-anchor-id="létat-de-lart-à-lheure-daujourdhui">L’état de l’art… à l’heure d’aujourd’hui</h2>
<p>Je n’avais pas suivi de très près l’évolution des modèles de génération d’image, mais je sais que <strong>ChatGPT</strong> d’OpenAI a fait beaucoup parler de lui, notamment avec sa capacité à produire des images “à la Ghibli” (avec les problèmes de copyright que cela a <a href="https://lareclame.fr/chatgpt-studioghibli-313028">posé</a>). Et <strong>Nano Banana</strong> de Google s’est aussi distingué ces derniers temps, les 2 entreprises se partagent le podium de la génération d’image sur <a href="https://llm-stats.com/">LLM Stats</a>.</p>
<p>Mais ce n’est pas la direction que je souhaitais prendre : je n’aime pas être contraint par des solutions propriétaires dont l’évolution rapide (prix, fin de produit, etc.), peut m’amener à changer de solution tous les 4 matins. Le décomissionnement récent de <a href="https://www.franceinfo.fr/internet/intelligence-artificielle/openai-ferme-sora-son-application-de-videos-generees-par-ia_7892921.html">Sora</a> (toujours de OpenAI) en est un bon rappel.</p>
<p>Il reste donc les solutions <strong>open source</strong>, heureusement fort nombreuses et de qualité. Il y a déjà la famille des Stable Diffusion qui a été aux prémices de la génération d’image par IA dans le domaine du libre, et dont le modèle <a href="https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0">XL</a> est toujours en tête des téléchargements sur Hugging Face. Mais c’est surtout la famille des modèles <strong>Flux</strong> de Black Forest Labs qui fait école aujourd’hui, avec des images générées de qualité vraiment impressionnante. Mais, comme pour les solutions propriétaires, le paysage évolue très vite et des challengers apparaissent tous les jours : Z Image, Qwen Image, … Comme pour les LLM de génération de texte, beaucoup de ces nouveaux modèles viennent de Chine et ont une approche plus sobre en termes de configuration requise.</p>
</section>
</section>
<section id="exploration" class="level1">
<h1>Exploration</h1>
<section id="les-constantes" class="level2">
<h2 class="anchored" data-anchor-id="les-constantes">Les constantes</h2>
<p>Ces présentations faites, il est temps de passer aux choses sérieuses et d’écrire du code pour générer ces fameux visuels. Je le disais tout à l’heure, mon idée est d’avoir une identité visuelle inspirée des comics strips de journaux. J’ai donc écrit un prompt générique pour le style graphique, en m’aidant d’un LLM (Claude Sonnet 4.6). Le prompt est en anglais, certains modèles ne supportant que cette langue en input.</p>
<div id="b1d4315c" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">STYLE_ANCHOR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""Pen and ink illustration in the tradition of newspaper comic strips.</span></span>
<span id="cb1-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Style evokes Bill Watterson's Calvin and Hobbes: expressive brushwork,</span></span>
<span id="cb1-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">confident ink linework, light crosshatching for shadows, generous</span></span>
<span id="cb1-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">whitespace, and occasional sparse watercolor-like tints — never flat fills.</span></span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Background: off-white paper texture, warm and slightly aged.</span></span>
<span id="cb1-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Palette: near-monochrome ink with at most 2 restrained accent colors,</span></span>
<span id="cb1-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">applied sparingly as washes rather than fills.</span></span>
<span id="cb1-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">No gradients, no digital effects, no screen tones, no halftone dots.</span></span>
<span id="cb1-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">No embedded text, no speech bubbles, no captions.</span></span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Characters when present: anthropomorphic animals rendered with</span></span>
<span id="cb1-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">expressive body language and clear silhouettes. Postures tell the story</span></span>
<span id="cb1-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">without words — curiosity, concentration, mild exasperation, wonder.</span></span>
<span id="cb1-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Line quality varies: thick confident outlines, thin detail lines inside.</span></span>
<span id="cb1-16"></span>
<span id="cb1-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Objects and settings: simplified but recognizable, drawn with the economy</span></span>
<span id="cb1-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">of a skilled cartoonist. Machinery looks mechanical and slightly whimsical.</span></span>
<span id="cb1-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Negative space is used deliberately — the white of the paper is part of the composition.</span></span>
<span id="cb1-20"></span>
<span id="cb1-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Mood: warm, slightly wry, intellectually playful. The kind of illustration</span></span>
<span id="cb1-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">that makes a technical concept feel approachable and human."""</span></span></code></pre></div></div>
</div>
<p>Enfin, définissons une “scène” qui sera rajoutée au prompt, et qui sera différente pour tous les visuels à créer. Je vais générer ici un visuel pour… ce même article (que c’est méta !). Partons sur un renard dessinant sur une table de dessinateur.</p>
<div id="7a4e5dc3" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">SCENE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">fox artist working at a large professional drafting table, slanted drawing board, sketching with precision, studio setting</span></span>
<span id="cb2-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span></code></pre></div></div>
</div>
<p>Finissons enfin par quelques constantes : le seed (pour la reproducibilité), le nombre d’images à générer et leurs dimensions.</p>
<div id="b5ef815e" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">SEED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span></span>
<span id="cb3-2">N_IMAGES <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb3-3">WIDTH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span></span>
<span id="cb3-4">HEIGHT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">576</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this generates a 16:9 ratio. ideal for header image</span></span></code></pre></div></div>
</div>
</section>
<section id="hugging-face-spaces" class="level2">
<h2 class="anchored" data-anchor-id="hugging-face-spaces">Hugging Face spaces</h2>
<p>Hugging Face propose des <a href="https://huggingface.co/spaces">spaces</a> qui exposent des modèles d’IA gratuitement : LLM, génération d’image, OCR, etc. Ils sont disponibles directement sans même s’enregistrer sur le site, mais avec un quota limité tout de même. Mieux : ils sont utilisables via API (sans token !), et sont généralement bien documentés.</p>
<p>En utilisant leur package, c’est d’une simplicité déconcertante, par exemple avec <strong>Flux 2 Klein 9B</strong> :</p>
<div id="24c9e7e5" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> gradio_client <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Client, handle_file</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> shutil</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pathlib <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Path</span>
<span id="cb4-4"></span>
<span id="cb4-5">Path(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs"</span>).mkdir(exist_ok<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N_IMAGES):</span>
<span id="cb4-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># code générant un fichier à partir du prompt. Le fichier est stocké dans un dossier temporaire stocké dans result</span></span>
<span id="cb4-9">    client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Client(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"black-forest-labs/FLUX.2-klein-9B"</span>)</span>
<span id="cb4-10">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.predict(</span>
<span id="cb4-11">        prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>STYLE_ANCHOR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> SCENE,</span>
<span id="cb4-12">        mode_choice<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Distilled (4 steps)"</span>, <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Utilise la version distillée pour génération plus rapide</span></span>
<span id="cb4-13">        seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>SEED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i, <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># on change le seed entre les 2 images (pour ne pas que ce soit les mêmes)</span></span>
<span id="cb4-14">        width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>WIDTH,</span>
<span id="cb4-15">        height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HEIGHT,</span>
<span id="cb4-16">        num_inference_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb4-17">        guidance_scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb4-18">        prompt_upsampling<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb4-19">        api_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/generate"</span></span>
<span id="cb4-20">    )</span>
<span id="cb4-21"></span>
<span id="cb4-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># on déplace le fichier à l'endroit souhaité</span></span>
<span id="cb4-23">    destination_folder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Path(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"./outputs/"</span>)</span>
<span id="cb4-24">    new_filename <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"hf_flux2_klein_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.png"</span>  </span>
<span id="cb4-25">    final_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(destination_folder, new_filename)</span>
<span id="cb4-26">    shutil.copy2(result[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], final_path)</span></code></pre></div></div>
</div>
<p>Et voici les résultats, générés en l’espace de quelques secondes :</p>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/hf_flux2_klein_0.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/hf_flux2_klein_0.png" class="img-fluid"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/hf_flux2_klein_1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/hf_flux2_klein_1.png" class="img-fluid"></a></p>
</div>
</div>
</div>
<p>C’est très bon ! Bien dans la tonalité que je recherche.</p>
<p>Cependant le fait de dépendre d’un service cloud, qui peut lui aussi évoluer très vite, me chiffonne. Les spaces sont très pratiques mais changent rapidement, en fonction des nouveaux modèles qui arrivent, et l’offre de Hugging Face ne restera très probablement pas éternellement gratuite.</p>
<p>Mon idée est donc de voir pour faire tourner ces modèles en local.</p>
</section>
<section id="en-local" class="level2">
<h2 class="anchored" data-anchor-id="en-local">En local</h2>
<p>Petit point sur ma config avant d’aller plus loin. J’utilise un HP Zbook G5, un PC portable <em>workstation</em> qui est une petite bombe… de 2018. Je l’ai effectivement acheté reconditionné, et son processeur Xeon E-2186M (proche d’un Intel Core i7 de 8e génération) et sa carte Quadro P3200 avec 6 Go de VRAM ne sont pas nés de la dernière pluie.</p>
<p>Je l’utilise régulièrement pour mes projets ML / LLM sans problème. J’arrive à faire de l’<em>inférence</em> de LLM qui ont une taille de 10-15B paramètres (en version quantisée bien sûr), les modèles se chargeant partiellement en VRAM, et le reste sur ma RAM système (ce qu’on appelle <em>offload</em> dans le jargon). J’étais donc confiant dans la capacité de ma config à faire tourner par exemple le Flux 2 Klein 9B que l’on a vu, même lentement…</p>
<section id="génération-dimage-une-conception-très-différente-des-llm" class="level3">
<h3 class="anchored" data-anchor-id="génération-dimage-une-conception-très-différente-des-llm">Génération d’image : une conception très différente des LLM</h3>
<p>Sauf que j’ai appris, bien à mes dépends, que les modèles de génération d’image différent des LLM sur ce point. Je passerai le nombre d’erreurs d’Out Of Memory (OOM) à répétition que j’ai connu avant de comprendre ce point.</p>
<p>Je ne vais pas rentrer en détails sur l’explication technique, ça sera sûrement l’objet d’un autre article, mais là où un LLM doit stocker en mémoire les poides du modèle et les tokens qu’il a produit (via le KV cache), un modèle de génération d’image stocke les poids du modèle, <strong>tous</strong> les tokens de l’image à générer (donc plus l’image est grande, plus c’est lourd) et les tenseurs d’attention, qui croissent de manière <strong>quadratique</strong> avec la taille de l’image. Ajoutons à ça que ces modèles sont généralement conçus pour tourner exclusivement sur GPU, sans mécanisme d’offload, et cela donne le combo gagnant pour saturer la pauvre VRAM de mon PC.</p>
<p>Heureusement, il y a des modèles très légers - notamment distillés - qui donnent des résultats acceptables. Et des outils permettent maintenant de limiter ce problème du chargement exclusif sur la VRAM, notamment OpenVINO. Commençons par voir un modèle très léger de Stable AI : leur modèle phare SDXL en distillé Turbo.</p>
</section>
<section id="sdxl-turbo" class="level3">
<h3 class="anchored" data-anchor-id="sdxl-turbo">SDXL Turbo</h3>
<p>SDXL est une version distillée du fameux modèle SDXL, pour rappel le modèle de génération d’image le plus téléchargé de Hugging Face. Cette version distillée permet d’arriver rapidement, en quelques étapes, à une image finale, là où le modèle de base en utilise bien plus (en général 20-50). L’idée est d’aller vite, au détriment d’un peu de perte de qualité, mais ce qui est intéressant pour des configs restreintes comme la mienne.</p>
<p>SDXL étant un modèle assez ancien, il utilise un encodeur de texte, CLIP, qui a une capacité maximale de <strong>77 tokens</strong>. Autant dire que la longue description de <code>STYLE_ANCHOR</code> est bien trop importante pour tenir dans cette petite fenêtre. Réécriture donc obligatoire, en se limitant fortement sur le nombre de mots.</p>
<div id="8a6d84ed" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">STYLE_ANCHOR_SHORT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb5-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">pen and ink, newspaper comic strip, calvin &amp; hobbes style,</span></span>
<span id="cb5-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">large expressive head, simple rounded body, anthropomorphic animal character,</span></span>
<span id="cb5-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">clean thin linework, off-white background, watercolor spot color,</span></span>
<span id="cb5-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">no gradients, no digital effects, no text</span></span>
<span id="cb5-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span></code></pre></div></div>
</div>
<p>Ensuite, place à l’instanciation du modèle :</p>
<div id="c57c2353" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> diffusers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> AutoPipelineForText2Image</span>
<span id="cb6-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb6-3"></span>
<span id="cb6-4">pipe_t1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AutoPipelineForText2Image.from_pretrained(</span>
<span id="cb6-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stabilityai/sdxl-turbo"</span>,</span>
<span id="cb6-6">    torch_dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float32,</span>
<span id="cb6-7">    variant<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fp16"</span></span>
<span id="cb6-8">)</span>
<span id="cb6-9">pipe_t1.enable_attention_slicing()</span></code></pre></div></div>
</div>
<p>Et la création des images :</p>
<div id="83e3fc43" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N_IMAGES):</span>
<span id="cb7-2">    seed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SEED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i</span>
<span id="cb7-3">    generator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.Generator(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cpu"</span>).manual_seed(seed)</span>
<span id="cb7-4"></span>
<span id="cb7-5">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pipe_t1(</span>
<span id="cb7-6">        prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>STYLE_ANCHOR_SHORT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> SCENE,</span>
<span id="cb7-7">        width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>WIDTH,</span>
<span id="cb7-8">        height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HEIGHT,</span>
<span id="cb7-9">        num_inference_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># j'ai choisi de partir sur 3 étapes, les résultats étant étrangement meilleurs que ceux à 4, le maximum "pertinent" d'étapes d'après la doc officielle</span></span>
<span id="cb7-10">        guidance_scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>,</span>
<span id="cb7-11">        generator<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>generator,</span>
<span id="cb7-12">    )</span>
<span id="cb7-13"></span>
<span id="cb7-14">    img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> result.images[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb7-15">    img.save(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"outputs/sdxl_turbo_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.png"</span>)</span></code></pre></div></div>
</div>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sdxl_turbo_0.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sdxl_turbo_0.png" class="img-fluid"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sdxl_turbo_1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sdxl_turbo_1.png" class="img-fluid"></a></p>
</div>
</div>
</div>
<p>On le voit tout de suite, on est pas du tout sur le même niveau de qualité que le Flux 2 Klein qu’on a vu précédemment. On sent même des faux raccords : le bras du 1er renard, la présence d’un … 2e renard, etc.</p>
<p>Bref, même si la génération est ok, on est de l’ordre de 1 min par image, la qualité n’est clairement pas suffisante pour mon cas d’usage. Pour être honnête, je ne suis pas dans sa zone de confort, étant donné que je demande des dimensions de 1024 x 576 là où le modèle recommandes l’utilisation de dimensions plus modestes (512 x 512). Ce qui explique sûrement</p>
<p>Avant d’attaquer un nouveau modèle, un peu de cleaning pour libérer la RAM de mon PC.</p>
<div id="1250745f" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> gc</span>
<span id="cb8-2"></span>
<span id="cb8-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">del</span> pipe_t1</span>
<span id="cb8-4">gc.collect()</span></code></pre></div></div>
</div>
</section>
<section id="sdxl-lightning" class="level3">
<h3 class="anchored" data-anchor-id="sdxl-lightning">SDXL Lightning</h3>
<p>Lightning est une autre variante distillée de SDXL, sortie un an plus tard que Turbo, en 2024. En se basant sur un autre type de distillation, la promesse est de générer des images de meilleure qualité, et notamment de plus grande dimension que Turbo (ouf !).</p>
<p>Ce modèle Lightning s’utilise <em>en plus</em> du modèle SDXL qui doit être chargé en RAM / VRAM. Comme je suis déjà très limite avec ma configuration, je suis passé par une version packagifiée de ces 2 modèles qui a été faite dans le cadre du projet <a href="https://github.com/rupeshs/fastsdcpu">FastSD CPU</a>. L’idée derrière cette initiative est de permettre d’utiliser exclusivement son CPU (et donc sa RAM) pour de ma génération d’image, en proposant un client web et des modèles refondus pour l’occasion.</p>
<p>Pour utiliser uniquement le CPU, ce projet s’appuie sur OpenVINO qu’on a déjà évoqué plus haut. On ne passe donc plus par la librairie Diffusers de Hugging Face, mais par optimum, qui a une API très proche. A noter que FastSD CPU propose surtout un outil en client web pour générer ces images, mais je préfère l’approche code pour plus de flexibilité.</p>
<div id="173ee394" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> optimum.intel <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OVStableDiffusionXLPipeline</span>
<span id="cb9-2"></span>
<span id="cb9-3">pipe_t2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OVStableDiffusionXLPipeline.from_pretrained(</span>
<span id="cb9-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rupeshs/SDXL-Lightning-2steps-openvino-int8"</span>,</span>
<span id="cb9-5">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>,</span>
<span id="cb9-6">)</span>
<span id="cb9-7">pipe_t2.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<div id="923c7ab5" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(N_IMAGES):</span>
<span id="cb10-2">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pipe_t2(</span>
<span id="cb10-3">        prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>STYLE_ANCHOR_SHORT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> SCENE,</span>
<span id="cb10-4">        width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>WIDTH,</span>
<span id="cb10-5">        height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HEIGHT,</span>
<span id="cb10-6">        num_inference_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb10-7">        guidance_scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb10-8">    ) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Note : pas de seed ici. Non supporté par OpenVINO</span></span>
<span id="cb10-9"></span>
<span id="cb10-10">    img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> result.images[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb10-11">    img.save(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"outputs/sdxl_lightning_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.png"</span>)</span></code></pre></div></div>
</div>
<div id="8b3f22ad" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">del</span> pipe_t2</span>
<span id="cb11-2">gc.collect()</span></code></pre></div></div>
</div>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sdxl_lightning_0.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sdxl_lightning_0.png" class="img-fluid"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sdxl_lightning_1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sdxl_lightning_1.png" class="img-fluid"></a></p>
</div>
</div>
</div>
<p>On voit qu’on a passé un cap au niveau de la qualité du rendu : plus de loupé dans le rendu, plus fin dans les détails, etc. Si je trouve le ton un peu trop “adulte” pour l’aspect comic strips que je recherche, cela pourrait être facilement amélioré à travers un fine tuning du prompt.</p>
<p>Je pourrais partir sur ce modèle, mais en regardant le repo de FastSD CPU, j’ai vu qu’ils ont récemment porté le modèle Sana Sprint de Nvidia qui est encore plus récent (2025).</p>
</section>
<section id="sana-sprint-0.6b" class="level3">
<h3 class="anchored" data-anchor-id="sana-sprint-0.6b">SANA Sprint 0.6B</h3>
<p>Sana Sprint est la version distillée de <a href="https://nvlabs.github.io/Sana/">Sana</a>, un modèle de génération d’image developpé par Nvidia Labs. Derrière l’utilisation d’un nouveau type d’encodage, la promesse de ce modèle est d’être plus efficace (comprendre : moins gourmand) dans l’inférence d’images. Le modèle étant plus récent, il s’appuie enfin sur T5 pour l’encodage du prompt ce qui permet d’accéder à une fenêtre de 512 token, vs les 77 de CLIP.</p>
<p>Malgré la petite taille du modèle distillé (seulement 0.6B !), le modèle demande quand même <a href="https://nvlabs.github.io/Sana/docs/installation/">9Go</a> de VRAM. Je passe donc par la version portée par FastSD CPU pour faire quelques tests.</p>
<div id="cdc06abd" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> optimum.intel <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OVPipelineForText2Image</span>
<span id="cb12-2"></span>
<span id="cb12-3">pipe_t3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OVPipelineForText2Image.from_pretrained(</span>
<span id="cb12-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rupeshs/sana-sprint-0.6b-openvino-int4"</span>,</span>
<span id="cb12-5">    ov_config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CACHE_DIR"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>},</span>
<span id="cb12-6">)</span></code></pre></div></div>
</div>
<div id="a8b5e4af" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>):</span>
<span id="cb13-2">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pipe_t3(</span>
<span id="cb13-3">        prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>STYLE_ANCHOR <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> SCENE,</span>
<span id="cb13-4">        width<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>WIDTH,</span>
<span id="cb13-5">        height<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>HEIGHT,</span>
<span id="cb13-6">        num_inference_steps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb13-7">        guidance_scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.5</span>,</span>
<span id="cb13-8">    )</span>
<span id="cb13-9"></span>
<span id="cb13-10">    img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> result.images[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-11">    img.save(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"outputs/sana_sprint_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.png"</span>)</span></code></pre></div></div>
</div>
<div id="60bc1c33" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">del</span> pipe_t3</span>
<span id="cb14-2">gc.collect()</span></code></pre></div></div>
</div>
<div class="quarto-layout-panel" data-layout-ncol="2">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sana_sprint_0.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sana_sprint_0.png" class="img-fluid"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 50.0%;justify-content: center;">
<p><a href="outputs/sana_sprint_1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-8"><img src="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sana_sprint_1.png" class="img-fluid"></a></p>
</div>
</div>
</div>
<p>Encore une fois, je trouve qu’un palier a été passé au niveau de la qualité d’image. Si on n’est pas encore au niveau d’un Flux, et qu’il y a même encore un peu de déchet (une petite queue de renard en trop…), on est clairement sur quelque chose d’exploitable pour le blog.</p>
</section>
</section>
</section>
<section id="conclusion" class="level1">
<h1>Conclusion</h1>
<p>Ce petit tour d’horizon des modèles de génération d’image m’a appris pas mal de choses :</p>
<ul>
<li>après toutes ces erreurs mémoires (OOM), j’ai maintenant compris que leur conception différente, et notamment le fait de devoir stocker tous les tokens de l’image plus les tenseurs d’attention font que leur besoin en VRAM (ou même en RAM) sont très importants ! Ce n’est pas demain que je vais pouvoir charger un modèle Flux sur mon PC.</li>
<li>Les modèles distillés permettent de réduire drastiquement le temps de génération d’image. Je vais sûrement consacrer un article dessus car je trouve l’approche très intéressante.</li>
<li>Comme pour les LLM classiques, les avancées technologiques et autres optimisations ont permis d’avoir des modèles beaucoup plus légers qui arrivent à des niveaux de qualité vraiment intéressants. Je garde un oeil sur les modèles qui vont arriver sur 2026 !</li>
</ul>
<p>Une de mes prochaines étapes sera de monter un petit projet de gestion de bibliothèque d’image, afin de me permettre de gérer au mieux la génération et la gestion de ces “assets” graphiques. Et qui sait, permettre de facilement changer et tout regénérer les images du blog si un nouveau modèle prometteur arrive.</p>


</section>

 ]]></description>
  <category>outils</category>
  <category>genai</category>
  <guid>https://blog.benhafed.com/articles/blog-visual_generation/</guid>
  <pubDate>Mon, 06 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://blog.benhafed.com/articles/blog-visual_generation/outputs/sana_sprint_0.png" medium="image" type="image/png" height="81" width="144"/>
</item>
</channel>
</rss>
