Sélectionner une page
Compiler un manuscrit Obsidian en document Word avec les styles conservés

Compiler un manuscrit Obsidian en document Word avec les styles conservés

Tu as rédigé ton livre par chapitres dans Obsidian avec Longform (voir l’article précédent), et il faut maintenant en sortir un fichier Word exploitable, avec de vrais styles Titre 1, Titre 2, Titre 3. Voici la chaîne d’outils qui fonctionne, sans installer d’extension système lourde.

Pourquoi une exportation directe ne suffit pas

On se heurte à deux difficultés si on cherche à lire directement le fichier compilé dans Microsoft Word :

  • Les images ne sont pas intégrées au document compilé. Elles sont simplement reliées. Et ne s’affichent dans le document que si on le lit dans un lecteur de type VScode et seulement si le fichier est dans le même répertoire que les images.
  • Word ne peut pas (cf explication ci-dessous) lire correctement les balises <h1>, <h2>, <h3> en styles natifs Word Titre 1, Titre 2, Titre 3 : il les affiche comme des paragraphes « Normal » avec une mise en forme visuelle appliquée directement (gras, taille, couleur). En enregistrant ensuite en .docx, cette erreur est figée : le fichier final n’a aucun style de titre exploitable.

On passe donc par un enregistrement en mode Page Web, complète (.htm;.html) via un navigateur web pour que les images soient correctement incorporées au document. Et ensuite on utilise LibreOffice car il a un moteur de conversion HTML qui associe strictement les balises <h1>, <h2>, <h3> aux vrais styles Titre 1, Titre 2, Titre 3 du format .docx.

Vue d’ensemble de la chaîne d’outils

Obsidian (Longform)
      ↓ compilation (fichier manuscrit.md)
VSCodium (export HTML Offline)
      ↓ (fichier manuscrit.html)
Navigateur web (Enregistrer en "Page Web complète .htm")
      ↓ (fichier manuscrit.html qui contient ses propres images)
Gestionnaire de fichier windows (renommage en .doc)
      ↓ (fichier manuscrit.doc)
LibreOffice Writer (pivot de conversion vers .docx)
      ↓ (fichier manuscrit.docx avec titres en styles et pas en mode "normal")
Microsoft Word ou Google Docs (fichier final avec styles modifiables)

Étape 1 : compiler le manuscrit avec Longform

Toutes tes images doivent être regroupées dans un seul dossier du vault (_Illustrations dans l’article précédent), sans quoi elles ne suivront pas lors de l’export.

Dans le panneau Longform, onglet Compile :

  1. Vérifie l’ordre des scènes/chapitres (glisser-déposer si besoin)
  2. Coche Strip Frontmatter, pour supprimer les métadonnées YAML du haut de chaque fichier
  3. Coche ou décoche Insert Chapter Titles selon que tes titres de chapitre sont déjà écrits dans le texte de chaque scène, ou seulement définis dans les propriétés
  4. Lance la compilation

Longform génère un fichier unique, par exemple manuscrit.md, dans le dossier de tes scènes.

Étape 2 : exporter en HTML avec VSCodium

Le fichier à exporter doit être placé dans le dossier _Illustrations, sans quoi les images ne suivront pas. Le fichier HTML se crée aussi dans ce dossier.

  1. Ouvre le fichier compilé (manuscrit.md) avec VSCodium
  2. Ouvre l’aperçu Markdown Preview Enhanced (Ctrl+K puis V). Si nécessaire installe l’extension Markdown Preview Enhanced dans VSCodium.
  3. Dans le panneau d’aperçu, clic droit n’importe où sur la page
  4. Choisis HTML puis HTML (Offline)

Résultat : un fichier manuscrit.html est créé à côté de tes fichiers Markdown.

Étape 3 : « Page Web complète » depuis le navigateur

Le fichier HTML doit lui aussi être dans _Illustrations pour cette étape. Le fichier .htm obtenu, lui, pourra être déplacé ailleurs ensuite, car il emporte ses images avec lui.

  1. Ouvre manuscrit.html dans un navigateur (Chrome, Edge, Firefox)
  2. Clic droit sur la page, Enregistrer sous… (ou Ctrl+S)
  3. Dans le champ Type, sélectionne impérativement Page Web, complète (.htm;.html)
  4. Choisis le dossier de destination et valide

Résultat : un fichier .html (par exemple manuscritComplet.html) qui intègre les images.

Étape 4 : pivot par LibreOffice Writer

C’est le point le plus sensible de la chaîne. Ouvrir directement le fichier .htm dans LibreOffice Writer ne fonctionne pas de façon fiable, le résultat dépend de la méthode d’ouverture utilisée. La seule méthode qui fonctionne à coup sûr :

  1. Fais une copie du fichier .htm
  2. Renomme cette copie avec l’extension .doc (par exemple manuscritComplet.doc)
  3. Ouvre ce fichier .doc avec LibreOffice Writer
  4. Vérifie que les niveaux de titre sont bien reconnus (Titre 1, Titre 2, Titre 3 dans le volet des styles)
  5. Enregistre sous format .docx

Étape 5 : finaliser dans Word ou Google Docs

Ouvre le fichier .docx obtenu avec Microsoft Word, ou dépose-le dans Google Drive pour l’ouvrir avec Google Docs.

  • Les titres sont reconnus avec les vrais styles Word (Titre 1, Titre 2, Titre 3), modifiables en un clic depuis le volet des styles
  • Les images apparaissent à leur emplacement, mais pas à la largeur définie dans Obsidian, il faut les redimensionner manuellement (voir la macro ci-dessous)
  • Le fichier .docx est autonome, il n’a plus besoin des fichiers sources

Redimensionner et centrer les images automatiquement

Écrire ![[image.png|550]] dans Obsidian ne suffit pas à contrôler la taille finale dans Word : les images arrivent à leur taille d’origine après le passage par la chaîne d’export. Cette macro Word parcourt le document et réduit à 15 cm toutes les images qui dépassent cette largeur, proportions conservées, puis les centre.

  1. Dans Word, Alt + F11 pour ouvrir l’éditeur de macro (ou onglet Développeur > Visual Basic)
  2. Menu Insert > Module
  3. Colle le code suivant :

vba

Sub RedimensionnerEtCentrerImages()
    Dim img As InlineShape
    Dim maxLargeurCm As Single
    Dim maxLargeurPoints As Single
    
    ' Définition de la largeur max à 15 cm
    maxLargeurCm = 15
    maxLargeurPoints = CentimetersToPoints(maxLargeurCm)
    
    ' Parcours de toutes les images du document
    For Each img In ActiveDocument.InlineShapes
        If img.Type = wdInlineShapePicture Or img.Type = wdInlineShapeLinkedPicture Then
            ' Redimensionnement si nécessaire
            If img.Width > maxLargeurPoints Then
                img.LockAspectRatio = msoTrue
                img.Width = maxLargeurPoints
            End If
            
            ' Alignement centré du paragraphe contenant l'image
            img.Range.ParagraphFormat.Alignment = wdAlignParagraphCenter
        End If
    Next img
    
    MsgBox "Toutes les images ont été ajustées (15 cm max) et centrées !", vbInformation, "Terminé"
End Sub
  1. Enregistre : Word te proposera de conserver le format .docx sans macro, refuse et enregistre le document au format acceptant les macros
  2. F5 (ou le bouton Lecture) pour exécuter la macro

Une autre piste, non testée dans vscodium : Pandoc

Pandoc, avec l’extension « Obsidian Pandoc » ou en ligne de commande, permettrait probablement d’exporter directement vers un .docx exploitable, sans passer par tout ce détour. Je ne l’ai pas testé : ça suppose d’installer un outil externe dont je ne connaissais pas la fiabilité, pour un besoin ponctuel. Si tu es plus à l’aise avec l’installation d’outils en ligne de commande, ça vaut sans doute le coup d’essayer avant de te lancer dans la chaîne décrite ici.

Limites connues, pour une prochaine fois

  • Les propriétés de titre ne sont pas correctement reprises à la compilation : c’est le nom du fichier qui est utilisé comme titre, pas la propriété titre définie dans le modèle
  • La macro de redimensionnement s’applique à toutes les images sans distinction : des petites images déjà à la bonne taille (par exemple des schémas illustratifs) se retrouvent elles aussi recentrées, alors qu’elles devraient rester alignées avec le texte
  • Les tableaux ne sont pas redimensionnés à la largeur du texte, ni alignés verticalement au centre, un ajustement manuel reste nécessaire

Pour aller plus loin

Écrire un livre dans Obsidian : structurer son manuscrit sans s’y perdre

Écrire un livre dans Obsidian : structurer son manuscrit sans s’y perdre

Écrire un livre (un essai dans mon cas, pas un roman), ce n’est pas seulement rédiger : c’est aussi retrouver un personnage cité au chapitre 3 quand on l’évoque au chapitre 9, savoir quel chapitre est prêt et lequel ne l’est pas, et un jour assembler tout ça en un seul document. Voici comment répondre à ces besoins dans Obsidian.

Ce dont tu as besoin pour écrire un livre sans t’y perdre

Au fil de la rédaction, plusieurs besoins reviennent, quel que soit le sujet du livre :

  • Une structure globale : voir l’ensemble des chapitres, les réorganiser facilement, sans dépendre de l’ordre dans lequel les fichiers ont été créés
  • Un espace de travail par chapitre : rédiger un chapitre sans que les notes de travail (idées, sources, remarques à soi-même) se mélangent au texte final
  • Un endroit où vont se ranger les images et autres éléments que je colle dans les chapitres ou notes.
  • Une mémoire des personnages et des notions récurrentes : éviter d’oublier qu’un personnage a déjà été présenté ailleurs, ou de redéfinir deux fois la même notion. Si tu ancres tes idées dans des cas concrets comme moi, tu auras vite affaire à de nombreux personnages, dont la vie professionnelle inspirera tes lecteurs.
  • Une vue d’ensemble de l’avancement : savoir en un coup d’œil quels chapitres sont terminés, lesquels restent à relire, et quelles questions sont encore en suspens
  • Un moyen de générer le manuscrit complet : assembler tous les chapitres, dans l’ordre, en un seul fichier propre
  • Des sauvegardes régulières : un projet de plusieurs mois de travail ne peut pas reposer uniquement sur la synchronisation entre appareils

Comment y répondre : la solution retenue

Trois extensions Obsidian couvrent ces besoins, chacune sur un point précis.

BesoinRéponseExtension
Structure globale, réorganisation des chapitresPanneau dédié avec glisser-déposer, compilation en un clicLongform
Isoler le texte final des notes de travail, chapitre par chapitreUn fichier texte et un fichier notes par chapitreOrganisation du vault (pas d’extension)
Stocker les images et pièces jointes sans les disperser dans le vaultDossier dédié aux pièces jointes, configuré une fois pour toutesRéglage natif d’Obsidian (pas d’extension)
Fiches de personnages et de notions sans repartir de zéroModèles de fichiers pré-remplisTemplater
Vue d’ensemble de l’avancementTableau de bord qui se met à jour automatiquementDataview
Protection contre les fausses manipulationsHistorique automatique des fichiersFile Recovery (natif)
Sécurité du projet dans son ensembleSauvegarde périodique indépendante de la synchronisationVoir l’article dédié, mentionné plus bas

Concrètement : chaque chapitre vit dans son propre dossier, avec un fichier texte.md (ce que lira le lecteur) et des fichiers notes.md (tout le reste). Longform assemble tous les texte.md, dans l’ordre que tu définis, pour produire le manuscrit final. Templater crée automatiquement des fiches à structure identique pour chaque personnage ou notion récurrente, à partir de modèles que tu ne rédiges qu’une seule fois. Dataview scanne l’ensemble du vault pour construire un tableau de bord : état de chaque chapitre, tâches en attente, questions non résolues.

La suite de l’article détaille la mise en œuvre technique de chaque brique. Si la vision d’ensemble te suffit pour l’instant, tu peux t’arrêter ici et y revenir plus tard.

Mise en œuvre technique

On considère que tu as installé les extensions dans ton vault.

Organiser le vault

Chaque chapitre repose sur un fichier texte, et un ou plusieurs fichiers de notes :

  • texte.md : le texte destiné au lecteur, le seul compilé par Longform
  • notes.md et d’autres fichiers de notes si besoin : tout le reste (notes de travail, sources, instructions à soi-même)
mon-livre/
│
├── 00-Avant-propos/
│   ├── texte.md
│   └── notes.md
├── 01-Introduction/
├── Partie-1/
│   ├── Chapitre-01/
│   ├── Chapitre-02/
├── Partie-2/
│   ├── Chapitre-03/
├── _Personnages/          ← fiches des personnages ou cas cités
├── _Concepts/              ← fiches de notions transversales
├── _Illustrations/          ← toutes les pièces jointes 
├── _Templates/             ← modèles de fichiers
├── _Versions/               ← archives manuelles
└── _Tableau-de-bord.md    ← vue d'ensemble du projet

Le préfixe _ fait remonter ces dossiers en haut de l’explorateur de fichiers.

Ranger les images automatiquement

Pour éviter de disperser les illustrations dans les dossiers de chapitres, configure un dossier unique pour toutes les pièces jointes :

  1. Ouvre Paramètres > Fichiers et liens
  2. Dans « Emplacement par défaut des nouvelles pièces jointes », choisis « Dans le dossier indiqué ci-dessous »
  3. Indique _Illustrations

Toute image collée ou glissée dans un chapitre atterrit désormais automatiquement dans ce dossier, quel que soit l’endroit où tu la colles.

Convention de nommage

Pour retrouver facilement une image et savoir d’où elle vient, nomme-la selon le schéma partie-chapitre-description, par exemple :

P2-02-secret.png

P2 pour la partie 2, 02 pour le chapitre 2, secret pour une description courte du contenu.

Insérer une image dans un chapitre

Utilise la syntaxe Wikilink standard, avec le nom du fichier tel qu’il apparaît dans _Illustrations :

![[P2-02-secret.png]]

Pour limiter sa largeur d’affichage, ajoute la largeur en pixels après un | :

![[P2-02-secret.png|500]]

Cette image s’intègre automatiquement dans le manuscrit compilé par Longform, sujet détaillé dans l’article suivant.

Configurer Longform

Créer le projet

  1. Clic droit sur le dossier racine du vault
  2. Sélectionner « Créer un projet Longform »
  3. Nommer le projet
  4. Choisir le format multi-scènes

Longform crée un fichier pivot qui sert de colonne vertébrale au manuscrit.

Gérer l’ordre des chapitres

L’ordre des scènes se gère dans le panneau Longform (icône dans la barre latérale gauche), onglet « Scenes ». Les chapitres se réorganisent par glisser-déposer, sans éditer de fichier YAML à la main. L’indentation dans ce panneau permet de créer des hiérarchies (parties > chapitres) sans toucher aux dossiers physiques.

Gérer les versions de rédaction (Drafts)

Longform permet de créer plusieurs brouillons d’un même projet (V1, V2…) via l’onglet « Project ». Chaque brouillon conserve sa propre structure de scènes, ce qui permet de restructurer une V2 tout en gardant la V1 intacte.

Configurer Templater

Configuration initiale

Dans Paramètres > Templater :

  • Template folder location : _Templates
  • Trigger Templater on new file creation : activer
  • Folder Templates : associer chaque dossier à son modèle
DossierModèle
_Personnagesmodele-fiche-personnage
_Conceptsmodele-fiche-concept

Pour les dossiers de chapitres (modele-texte-chapitre.md et modele-notes-chapitre.md), mieux vaut passer par la palette de commandes au moment de créer le fichier (voir ci-dessous), plutôt que par une association automatique.

Utilisation au quotidien

Deux façons d’appliquer un modèle :

  • Créer un fichier dans un dossier associé : Templater l’applique automatiquement
  • Ou : Ctrl+P > « Templater : Insert template » > choisir le modèle manuellement

Les modèles de fichiers

J’ai créé 4 fichiers dans _Templates/ avant de commencer la rédaction. Je te montre le premier modèle. Les autres sont sur le même principe.

Modèle 1 : texte de chapitre

Nom du fichier : modele-texte-chapitre.md

---
type: texte
chapitre: &lt;% tp.file.folder(true).split('/').pop() %>
titre: &lt;% tp.system.prompt("Titre du chapitre ?") %>
etat: a-nettoyer
version: 1
date_creation: &lt;% tp.date.now("YYYY-MM-DD") %>
date_derniere_modif: &lt;% tp.date.now("YYYY-MM-DD") %>
relu: false
finalise: false
---

# &lt;% tp.frontmatter.titre %>

## Introduction du chapitre

## Développement

## Récapitulons

&lt;% tp.file.cursor() %>

Les valeurs possibles pour le champ etat : a-nettoyer / en-cours-nettoyage / nettoye / a-relire / finalise.

La procédure de nettoyage d’un chapitre

Sept étapes, à répéter pour chaque chapitre.

1. Créer les fichiers du chapitre

Dans le dossier du chapitre, crée texte.md et des fichiers notes.md à partir des modèles Templater.

2. Coller le contenu brut

Copie le contenu du chapitre depuis ton brouillon global. Colle-le dans texte.md.

3. Trier

A déplacer dans notes.mdA garder dans texte.md
« Illustration possible : … »Tout le texte rédigé
« Voir aussi [lien web] »Les citations
« A exploiter / à creuser »Les exemples et cas concrets
Sorties ChatGPT/Gemini brutesLes tableaux
Questions à soi-mêmeLe récapitulatif

4. Baliser les tâches restantes

Dans texte.md, signale les passages à retravailler avec la syntaxe de tâche Markdown :

- [ ] Revoir ce passage - manque d'exemple #todo
- [ ] Vérifier la cohérence avec le chapitre 3 #coherence

Dataview les centralisera automatiquement dans le tableau de bord.

5. Mettre à jour l’en-tête

Change etat et date_derniere_modif dans le bloc YAML.

6. Créer ou compléter les fiches

Pour chaque personnage ou concept mentionné dans le chapitre, vérifie s’il a une fiche dans _Personnages/ ou _Concepts/, crée-la si besoin.

7. Lier les chapitres entre eux

Dans certains fichiers de notes, section « Liens vers autres chapitres » :

- Renforce [[Chapitre-04/texte]]
- Contraste avec [[Chapitre-06/texte]]
- Lien avec concept [[_Concepts/Concept-A]]

Le tableau de bord avec Dataview

Contenu de _Tableau-de-bord.md. Les requêtes se mettent à jour automatiquement à chaque modification du vault.

Etat des chapitres

TABLE titre, etat, relu, finalise
FROM "/"
WHERE type = "texte"
SORT chapitre ASC

Tâches en suspens dans le manuscrit

TASK
FROM "/"
WHERE !completed
SORT file.name ASC

Questions en suspens

TABLE question AS "Question", file.name AS "Chapitre"
FROM "/"
WHERE question != null

Concepts par famille

TABLE famille
FROM "_Concepts"
WHERE type = "concept"
SORT famille ASC

Gérer les versions

Protection automatique : File Recovery

Obsidian sauvegarde automatiquement des instantanés de chaque fichier toutes les 5 minutes. Pour restaurer : Paramètres > Plugins principaux > File Recovery > View. Tu cherches le fichier, tu vois l’historique, tu restaures la version choisie.

Protection manuelle avant révision importante

Avant toute révision importante, notamment avant de travailler avec une IA :

  1. Copie le fichier texte.md dans _Versions/
  2. Renomme-le avec la date, par exemple Chapitre-03-2026-05-20.md
  3. Travaille sur le fichier original

Brouillons Longform pour les restructurations majeures

Si tu envisages de modifier l’ordre des chapitres ou de fusionner des parties, crée un nouveau Draft dans Longform plutôt que de modifier directement. La version précédente reste intacte pour comparaison ou restauration.

Sauvegardes : ne pas s’arrêter à la synchronisation

File Recovery protège contre une fausse manipulation locale, mais ni File Recovery ni la synchronisation ne protègent contre une suppression propagée sur tous les appareils, ou une corruption de fichier répliquée avant que tu t’en rendes compte. Une sauvegarde périodique, indépendante du mécanisme de synchronisation, reste indispensable pour un projet de cette ampleur.

Le sujet est détaillé dans Synchroniser Obsidian entre PC et Android avec Nextcloud, qui explique aussi comment aller plus loin avec une sauvegarde périodique automatisée.

Pour aller plus loin

Température et system prompt : ce qui fait varier un modèle local

Température et system prompt : ce qui fait varier un modèle local

Dans l’article précédent, j’ai comparé 5 modèles de langage installés sur un mini PC. Et j’ai réglé la « température » (quel drôle de mot) sur 0.2. Ca m’a fait me demander quelle était l’importance de la température d’un modèle de langage sur la qualité de ses résultats. Et je me suis rendue compte qu’il y a aussi une notion de « system prompt » qui peut avoir une incidence sur la qualité des résultats. Alors j’ai décidé de lancer un test, avec un seul modèle, qui fait varier les températures et le system prompt. Le résultat m’a beaucoup appris sur les modèles de langage.

Cet article fait partie de la série Créer une IA locale.

Deux réglages à comprendre avant de commencer

La température contrôle le degré d’imprévisibilité du texte généré. À chaque mot, le modèle calcule une probabilité pour plusieurs mots suivants possibles. Une température basse (proche de 0) pousse le modèle à toujours choisir le mot le plus probable, ce qui donne des réponses stables et répétitives. Une température plus haute laisse le modèle piocher parmi des mots moins probables, ce qui donne des réponses plus variées et plus créatives, mais aussi plus de risque de dérive ou d’invention.

Le system prompt est une instruction envoyée au modèle avant la conversation elle-même, invisible pour l’utilisateur final. Elle sert à cadrer le comportement du modèle, par exemple « réponds de façon concise » ou « tu es un assistant technique rigoureux ».

Un point important à connaître si tu passes d’une interface comme Open WebUI à un appel direct via l’API d’Ollama : Open WebUI injecte souvent un system prompt par défaut, invisible dans l’interface. Un appel direct à l’API d’Ollama, comme dans le script ci-dessous, n’en contient aucun sauf si tu le précises explicitement dans la requête. C’est une des raisons pour lesquelles un même modèle peut sembler se comporter différemment selon l’interface utilisée.

Le protocole

L’idée : tester Gemma 4 E2B sur le même prompt (logique, traduction, poésie) que dans le comparatif précédent, avec 5 répétitions par configuration, pour distinguer une tendance réelle du simple hasard.

Deux variables testées :

  • la température : 0,2, 0,5 et 0,8
  • la présence ou non d’un system prompt de concision

Soit 2 configurations de system prompt x 3 températures x 5 runs, un total de 30 exécutions.

À chaque run, le script exécute docker exec ollama ollama stop gemma4:e2b et attend 2 secondes pour garantir que le modèle est bien déchargé de la mémoire avant de relancer la génération suivante.

#!/bin/bash

MODEL="gemma4:e2b"

PROMPT="Résous ce problème de logique étape par étape : Trois personnes (Alice, Bob et Charlie) ont chacune une couleur de pull différente (Bleu, Rouge, Vert). Alice dit qu'elle ne porte pas de bleu. Charlie porte un pull vert. Quelle est la couleur du pull de Bob ? Ensuite, traduis cette expression anglaise de manière naturelle en français : 'It is raining cats and dogs'. Enfin, écris une seule phrase poétique sur la pluie."

SYSTEM_PROMPT="Tu es un assistant virtuel logique, précis et concis. Analyse les contraintes fournies sans inventer d'éléments extérieurs."

TEMPS=(0.2 0.5 0.8)
RUNS=5
OUTPUT_FILE="resultats_gemma.txt"

echo "=== BENCHMARK GEMMA 4 E2B : STABILITÉ & SYSTEM PROMPT ===" > "$OUTPUT_FILE"
echo "Date : $(date)" >> "$OUTPUT_FILE"
echo "--------------------------------------------------" >> "$OUTPUT_FILE"

# Boucle avec et sans System Prompt
for USE_SYS in "SANS" "AVEC"; do
  for TEMP in "${TEMPS[@]}"; do
    echo "=================================================="
    echo "Configuration : System Prompt [$USE_SYS] | Température [$TEMP]"
    echo "=================================================="
    echo "==================================================" >> "$OUTPUT_FILE"
    echo "CONFIG : System Prompt [$USE_SYS] | Température [$TEMP]" >> "$OUTPUT_FILE"
    echo "==================================================" >> "$OUTPUT_FILE"

    for ((i=1; i<=RUNS; i++)); do
      echo -ne "Exécution $i/$RUNS...\r"

      # Vider la mémoire RAM/VRAM avant chaque run
      docker exec ollama ollama stop "$MODEL" > /dev/null 2>&1
      sleep 2

      # Construction du payload selon la présence du system prompt
      if [ "$USE_SYS" == "AVEC" ]; then
        PAYLOAD=$(jq -n \
          --arg model "$MODEL" \
          --arg prompt "$PROMPT" \
          --arg system "$SYSTEM_PROMPT" \
          --argjson temp "$TEMP" \
          '{model: $model, prompt: $prompt, system: $system, stream: false, options: {temperature: $temp}}')
      else
        PAYLOAD=$(jq -n \
          --arg model "$MODEL" \
          --arg prompt "$PROMPT" \
          --argjson temp "$TEMP" \
          '{model: $model, prompt: $prompt, stream: false, options: {temperature: $temp}}')
      fi

      START_TIME=$(date +%s.%N)
      RESPONSE=$(curl -s http://localhost:11434/api/generate \
        -H "Content-Type: application/json" \
        -d "$PAYLOAD")
      END_TIME=$(date +%s.%N)
      ELAPSED=$(echo "$END_TIME - $START_TIME" | bc)

      TEXT=$(echo "$RESPONSE" | jq -r '.response // "Erreur"')
      EVAL_COUNT=$(echo "$RESPONSE" | jq -r '.eval_count // 0')
      EVAL_DURATION=$(echo "$RESPONSE" | jq -r '.eval_duration // 0')

      if [ "$EVAL_DURATION" -gt 0 ] 2>/dev/null; then
        TPS=$(echo "scale=2; $EVAL_COUNT / ($EVAL_DURATION / 1000000000)" | bc -l)
      else
        TPS="N/A"
      fi

      # Écriture des résultats dans le fichier log
      echo "--- Run $i/$RUNS (${ELAPSED}s | ${TPS} tok/s | ${EVAL_COUNT} tokens) ---" >> "$OUTPUT_FILE"
      echo "$TEXT" >> "$OUTPUT_FILE"
      echo "" >> "$OUTPUT_FILE"
    done

    echo -e "Configuration terminée !          "
  done
done

# Nettoyage final
docker exec ollama ollama stop "$MODEL" > /dev/null 2>&1
echo "=== FIN DU BENCHMARK ==="
echo "Tous les résultats ont été enregistrés dans : $OUTPUT_FILE"

Copie ce script dans un fichier test_gemma.sh, rends-le exécutable (chmod +x test_gemma.sh) et lance-le. Les résultats s’écrivent automatiquement dans resultats_gemma.txt. Compte-toi plusieurs heures d’exécution, 30 runs sur un modèle qui répond en 90 à 110 secondes en moyenne, ça prend du temps.

Résultats

ConfigurationSystem PromptTemp.Réussite logiqueRéussite traductionTemps moyen / runTokens moyens / runVitesse moyenne
Config 1SANS0,25/55/5107,90 s962 tok9,83 tok/s
Config 2SANS0,55/55/5107,96 s964 tok9,83 tok/s
Config 3SANS0,85/55/5109,22 s973 tok9,80 tok/s
Config 4AVEC0,25/55/591,23 s796 tok9,86 tok/s
Config 5AVEC0,55/55/589,92 s783 tok9,85 tok/s
Config 6AVEC0,85/55/594,63 s829 tok9,86 tok/s

tu peux voir tous les résultats dans ce fichier texte : 2026 09 02 resultats_gemma

Une stabilité remarquable sur la logique et la traduction

Sur les 30 exécutions, Gemma 4 E2B obtient un score parfait : 30 sur 30 sur la déduction logique et sur la traduction idiomatique. Même à une température de 0,8, le modèle ne dérive pas et ne se met pas à inventer une couleur ou une traduction fantaisiste.

L’effet du system prompt sur la concision

L’ajout du system prompt de concision change nettement le comportement du modèle :

  • le nombre moyen de tokens générés passe d’environ 966 (sans) à environ 802 (avec), soit une baisse d’environ 17%
  • le temps moyen d’exécution passe d’environ 108 secondes à environ 92 secondes par run
  • sans system prompt, le modèle a tendance à répéter l’énoncé et à ajouter des phrases d’introduction (« Voici la résolution… »), ce que le system prompt supprime

Une vitesse d’inférence constante

Sur l’ensemble des 30 runs, la vitesse brute reste comprise entre 9,80 et 9,87 tokens par seconde, quelle que soit la configuration. Ça confirme que ni la température ni le system prompt ne changent l’effort de calcul par token : ils jouent uniquement sur la longueur et la structure du texte généré.

Et la créativité poétique ?

À température 0,2, les métaphores se répètent beaucoup d’un run à l’autre, la même formulation revient plusieurs fois sur les 5 runs d’une même configuration. À température 0,8, le vocabulaire se diversifie nettement, tout en restant grammaticalement correct.

Ce que je retiens

Gemma 4 E2B se montre robuste sur les tâches factuelles, peu importe la température testée ici. Le vrai levier d’optimisation n’est pas la précision, elle est déjà excellente, mais la vitesse de traitement : ajouter un system prompt de concision réduit le temps de traitement d’environ 17% sans perdre en fiabilité.

Une limite à garder en tête

Ce test ne porte que sur Gemma 4 E2B. Il est possible que d’autres modèles réagissent différemment à un system prompt de concision, ça reste à vérifier avec le même protocole.

Pour aller plus loin

Cet article fait partie de la série Créer une IA locale. Retrouve aussi le comparatif complet dans Gemma 4 E2B face à 4 autres modèles LLM locaux et la série Projets Ubuntu.

Gemma 4 E2B face à 4 autres modèles LLM locaux : le crash test

Gemma 4 E2B face à 4 autres modèles LLM locaux : le crash test

J’ai comparé quatre modèles LLM en local dans un premier article, en juin. J’avais réalisé les essais à la main, en ligne de commande. Ici j’ai voulu comparer ces modèles à Gemma 4 E2B, un modèle récent. et j’ai choisi de le faire en automatique, via un script bash. Ma seule contribution pendant le comparatif, c’était de regarder la charge du système avec chaque modèle. J’étais chargée de faire des captures d’écran, le script faisait tout le reste !

Cet article fait partie de la série Créer une IA locale.

Pourquoi une température basse pour ce comparatif

La température est un réglage qui contrôle le degré d’imprévisibilité d’un modèle. Une température basse pousse le modèle vers la réponse la plus probable, donc plus stable et plus factuelle. Une température haute laisse plus de place à la variation, ce qui est utile pour la créativité mais augmente le risque d’inventer des éléments qui n’ont pas de sens.

Pour comparer des modèles sur leur capacité à raisonner et traduire correctement, j’ai réglé tous les modèles à une température de 0,2. Par défaut, Open WebUI utilise une température plus élevée (autour de 0,8), pensée pour des usages conversationnels plus créatifs. C’est un choix qui a un impact réel sur les résultats, je détaille cet aspect dans un article, ici, dédié à la température et au system prompt.

Avant de démarrer

Avant tout test, j’ai mis à jour tous mes containers Docker via Portainer (voir l’article dédié), pour être certaine que chaque modèle tourne sur la dernière version d’Ollama.

J’ai aussi arrêté temporairement les containers non essentiels pendant le test (Home Assistant, Tika, Stirling-PDF, Mosquitto, Zigbee2MQTT), pour éviter qu’ils ne consomment de la RAM ou du CPU en arrière-plan et faussent les mesures.

Un second terminal SSH avec htop ouvert en parallèle permet de suivre la consommation de RAM et de CPU pendant chaque test.

Le protocole

Les cinq modèles comparés, tous réglés à température 0,2 :

  • Gemma 4 E2B
  • Qwen 2.5 3B
  • Qwen 2.5 7B
  • Mistral 7B
  • Llama 3.1 8B

Chaque modèle reçoit le même prompt, qui combine trois tâches différentes : un problème de logique, une traduction d’expression idiomatique, et une phrase poétique. Ce format permet de juger plusieurs capacités en un seul appel, mais il faut garder en tête qu’un échec sur une tâche ne dit rien des deux autres.

Installe d’abord jq et bc, deux outils utilisés par le script :

bash

sudo apt install jq bc

jq est un processeur JSON en ligne de commande. Comme l’API d’Ollama renvoie ses réponses en JSON, jq sert à en extraire la réponse du modèle, le nombre de tokens générés ou le temps de calcul. bc est une calculatrice en ligne de commande qui gère les nombres à virgule, ce que Bash ne sait pas faire nativement, utile ici pour calculer la vitesse en tokens par seconde.

Crée ensuite le script :

sudo nano /home/USER/benchmark.sh

Colle-y ce contenu :

#!/bin/bash

MODELS=(
  "gemma4:e2b"
  "qwen2.5:3b"
  "qwen2.5:7b"
  "mistral:latest"
  "llama3.1:latest"
)

PROMPT="Résous ce problème de logique étape par étape : Trois personnes (Alice, Bob et Charlie) ont chacune une couleur de pull différente (Bleu, Rouge, Vert). Alice dit qu'elle ne porte pas de bleu. Charlie porte un pull vert. Quelle est la couleur du pull de Bob ? Ensuite, traduis cette expression anglaise de manière naturelle en français : 'It is raining cats and dogs'. Enfin, écris une seule phrase poétique sur la pluie."
JSON_PROMPT=$(jq -rn --arg x "$PROMPT" '$x')

TOTAL=${#MODELS[@]}
INDEX=1

echo "=== DÉBUT DU BENCHMARK ==="
echo "Température : 0.2"
echo "--------------------------------------------------"

for MODEL in "${MODELS[@]}"; do
  # Mise à jour du titre de l'onglet du terminal
  echo -ne "\033]0;[$INDEX/$TOTAL] Pulling $MODEL...\007"
  echo ""
  echo "[$INDEX/$TOTAL] >>> Mise à jour et récupération des infos de : $MODEL"

  # 1. Mise à jour du modèle
  docker exec ollama ollama pull "$MODEL" > /dev/null 2>&1

  # 2. Récupération des informations de version (Digest / Quantization)
  SHOW_INFO=$(curl -s http://localhost:11434/api/show -d "{\"model\": \"$MODEL\"}")
  FAMILY=$(echo "$SHOW_INFO" | jq -r '.details.family // "inconnu"')
  QUANT=$(echo "$SHOW_INFO" | jq -r '.details.quantization_level // "inconnu"')

  # Récupération de l'ID/Digest du modèle via Ollama CLI
  MODEL_ID=$(docker exec ollama ollama list | grep -E "^$(echo $MODEL | cut -d: -f1)" | awk '{print $2}' | head -n 1)

  # 3. Nettoyage VRAM/RAM
  docker exec ollama ollama stop "$MODEL" > /dev/null 2>&1
  sleep 3

  # Mise à jour du titre de l'onglet pendant le test
  echo -ne "\033]0;[$INDEX/$TOTAL] Testing $MODEL...\007"
  echo ">>> Exécution du test..."

  # 4. Envoi de la requête API et mesure
  START_TIME=$(date +%s.%N)

  RESPONSE=$(curl -s http://localhost:11434/api/generate -d "{
    \"model\": \"$MODEL\",
    \"prompt\": $JSON_PROMPT,
    \"options\": {
      \"temperature\": 0.2
    },
    \"stream\": false
  }")

  END_TIME=$(date +%s.%N)
  ELAPSED=$(echo "$END_TIME - $START_TIME" | bc)

  # Extraction des métriques
  TEXT=$(echo "$RESPONSE" | jq -r '.response')
  EVAL_COUNT=$(echo "$RESPONSE" | jq -r '.eval_count')
  EVAL_DURATION=$(echo "$RESPONSE" | jq -r '.eval_duration')

  TPS=$(echo "scale=2; $EVAL_COUNT / ($EVAL_DURATION / 1000000000)" | bc -l 2>/dev/null || echo "N/A")

  # 5. Affichage des résultats
  echo "--------------------------------------------------"
  echo "MODÈLE         : $MODEL"
  echo "ID (Digest)    : ${MODEL_ID:-N/A}"
  echo "Format / Quant : $FAMILY ($QUANT)"
  echo "Temps total    : ${ELAPSED}s"
  echo "Tokens générés : $EVAL_COUNT"
  echo "Vitesse        : ${TPS} tok/s"
  echo "--------------------------------------------------"
  echo "Réponse :"
  echo "$TEXT"
  echo "--------------------------------------------------"

  # Nettoyage
  docker exec ollama ollama stop "$MODEL" > /dev/null 2>&1
  ((INDEX++))
  sleep 2
done

# Remet le titre par défaut du terminal
echo -ne "\033]0;Terminal\007"
echo "=== BENCHMARK TERMINÉ ==="

Rends-le exécutable et lance-le :

sudo chmod +x benchmark.sh
./benchmark.sh

Le script se charge de :

  • mettre à jour chaque modèle avant de le tester
  • vider complètement la RAM entre deux modèles, pour ne pas fausser les mesures
  • envoyer le prompt via l’API d’Ollama avec la température fixée à 0,2
  • mesurer le temps de réponse total
  • calculer la vitesse de génération en tokens par seconde

Il n’enregistre en revanche pas les pics de RAM et de CPU du système, c’est pour cela qu’il faut suivre htop en parallèle dans un autre terminal. Voici par exemple une capture d’écran réalisée pendant le test de Gemma 4 E2B. Les 4 autres captures sont tout en bas de l’article :

htop pendant l'essai de Gemma 4 E2B

Résultats

ModèleLogiqueTraductionPoésieVitesse (tok/s)Temps totalRAM max (htop)
Gemma 4 E2B (5,1B)Juste (Bleu)Juste (Cordes)Juste9,84106,78 s9,16 Go
Qwen 2.5 (3B)Faux (Rouge)Faux (hors-sujet)Juste8,3739,63 s3,92 Go
Qwen 2.5 (7B)Faux (Rouge)Juste (Cordes)Juste3,7869,21 s6,54 Go
Mistral (7B)Faux (Rouge)Juste (Cordes)Juste3,8648,92 s6,48 Go
Llama 3.1 (8B)Juste (Bleu)Inexact (« gros traits »)Juste3,5590,98 s7,07 Go

Gemma 4 E2B est le seul modèle à réussir les trois épreuves sans erreur. Il génère aussi le texte le plus rapidement en tokens par seconde, même si son temps total reste élevé à cause d’une phase de réflexion plus longue avant de produire la réponse. Sa consommation RAM est la plus élevée du lot, ce qui s’explique par le contexte de raisonnement chargé en mémoire.

Les deux modèles Qwen 2.5 7B et Mistral 7B échouent tous les deux sur le problème de logique, en concluant à tort que Bob porte du rouge. Llama 3.1 8B résout correctement la logique mais traduit l’expression de façon moins idiomatique.

Les réponses des 5 modèles sont disponibles dans ce fichier texte : 2026 09 02 resultats_benchmark

Ce que j’en pense

Au vu de ces résultats, Gemma 4 E2B devient mon modèle local par défaut. Il n’est pas seulement le plus fiable des cinq sur ce test, c’est aussi le seul de la sélection à traiter nativement le texte, l’image et l’audio. Il peut aussi analyser des vidéos, en les découpant en une séquence d’images.

Cette capacité multimodale ouvre des usages que je n’avais pas avec les autres modèles testés ici, comme la transcription ou la description d’images directement en local. C’est une piste que je compte explorer dans un prochain article. Et je vais aussi l’utiliser comme une sorte d’agent.

Pour aller plus loin

Cet article fait partie de la série Créer une IA locale. Retrouve aussi la série Projets Ubuntu pour tout ce qui touche à l’infrastructure Linux qui héberge ces modèles.

Les copies d’ecran htop pendant les 4 autres essais

Pendant le test de qwen2.5:3b :

Pendant le test de qwen2.5:3b

Pendant le test de qwen2.5:7b :

Pendant le test de qwen2.5:7b

Pendant le test de mistral:latest

Pendant le test de Mistral

Pendant le test de llama3.1:latest

Pendant le test de llama3.1
Mettre à jour ses containers Docker proprement avec Portainer

Mettre à jour ses containers Docker proprement avec Portainer

Je voulais mettre à jour mes containers Docker avant de lancer une expérmentation. Je ne me suis pas vraiment posé de question, j’ai moins d’une dizaine de containers. J’ai choisi une méthode manuelle, via Portainer, sans perdre leur configuration.

La mise à jour manuelle via Portainer présente trois avantages :

  • tu choisis exactement quand la coupure de service a lieu
  • tu vois immédiatement dans les logs si le container redémarre correctement
  • tu n’ajoutes pas de container supplémentaire qui tourne en permanence

La méthode

Pour chaque container à mettre à jour, sans perdre sa configuration ni ses données (à condition qu’elles soient stockées sur des volumes ou des dossiers hôtes) :

  1. Dans Portainer, va dans Containers et clique sur le container concerné.
  2. Clique sur Re-create (ou Duplicate/Edit selon la version de Portainer) dans la barre d’outils.
  3. Active l’interrupteur Pull latest image.
  4. Valide en cliquant sur Re-create.

Portainer va chercher la nouvelle version de l’image sur Docker Hub, arrête l’ancien container et en relance un nouveau avec les mêmes variables d’environnement, ports et volumes.

Vérifier que la mise à jour s’est bien passée

Une fois le container recréé, vérifie dans Portainer :

  • que le statut passe bien à running
  • que les logs du container ne montrent pas d’erreur au démarrage
  • que le service reste accessible (par exemple, l’interface web du container si elle existe)

Si un container refuse de redémarrer après une mise à jour, la cause la plus fréquente est une incompatibilité entre la nouvelle version de l’image et la configuration existante (variable d’environnement renommée, changement de format de volume, etc.). Dans ce cas, les logs Portainer donnent en général une indication claire du problème.

Pour aller plus loin

Cet article fait partie de la série Projets Ubuntu, qui documente la gestion d’une infrastructure Linux personnelle.