La boîte à outils du LLM Engineer
L'outillage MLOps et cloud qui rend un système LLM reproductible : orchestration, suivi d'expériences, registres de modèles et bases vectorielles.
Construire un modèle de langage qui fonctionne dans un notebook est une chose ; le transformer en un système qu'on peut réentraîner, déployer et déboguer six mois plus tard en est une autre. C'est précisément le fossé que comble l'outillage MLOps. Iusztin et Labonne consacrent un chapitre entier à présenter leur pile technique avant même d'écrire la première ligne du projet fil rouge, le LLM Twin (un assistant entraîné à imiter votre style d'écriture). Leur message implicite est clair : on ne choisit pas des outils par goût, on les choisit parce qu'ils incarnent des principes d'ingénierie.
Ce chapitre n'est pas un guide d'installation pas à pas — les auteurs renvoient le lecteur au README du dépôt pour cela. Il s'agit plutôt de comprendre, catégorie par catégorie, quel problème chaque outil résout et pourquoi il a sa place dans un système de production. La règle d'or à garder en tête : ces outils ne sont jamais une fin en soi, ils sont la matérialisation concrète de trois exigences MLOps fondamentales — la reproductibilité (reproducibility), l'automatisation et le versionnage du code, des données et des modèles.
La philosophie MLOps : pourquoi avant comment
Le développement d'un modèle d'apprentissage automatique diffère radicalement du développement logiciel classique sur un point : c'est un processus itératif et expérimental. On lance des dizaines d'essais en parallèle, on les compare sur des métriques, et on décide lequel mérite la production. Sans discipline, ce chaos devient ingérable et, surtout, irreproductible : impossible de réexpliquer pourquoi le modèle déployé se comporte comme il le fait.
Trois piliers structurent toute la pile présentée dans le livre.
- Reproductibilité : pouvoir relancer un pipeline et obtenir le même résultat. Cela suppose de figer non seulement le code, mais aussi la version de Python, les dépendances exactes, les données d'entrée et les hyperparamètres.
- Automatisation : enchaîner les étapes (collecte, prétraitement, entraînement, déploiement) sans intervention manuelle, ce qui réduit les erreurs et accélère les itérations.
- Versionnage : suivre l'historique du code (Git), mais aussi des données et des modèles, qui changent indépendamment du code. Un même script produit des modèles différents selon le jeu de données ; il faut donc versionner les trois.
Note
Le livre repousse délibérément la théorie approfondie de MLOps et LLMOps au chapitre 11. L'idée pédagogique est qu'on comprend bien mieux ces concepts après avoir vu les outils à l'œuvre dans un cas concret. Ce chapitre-ci se contente donc de poser le vocabulaire et le rôle de chaque brique.
Avant d'entrer dans le détail, voici la cartographie d'ensemble, qui sert de fil conducteur au reste du chapitre.
| Catégorie d'outil | Problème résolu | Exemple retenu dans le livre |
|---|---|---|
| Gestion des versions de Python | Isoler la version d'interpréteur par projet | pyenv |
| Gestion des dépendances et environnements | Figer les paquets exacts, éviter les conflits | Poetry |
| Exécuteur de tâches | Centraliser les commandes CLI du projet | Poe the Poet |
| Orchestrateur de pipelines | Automatiser et tracer les enchaînements ML | ZenML |
| Suivi d'expériences | Comparer les essais, métriques, hyperparamètres | Comet ML |
| Registre de modèles | Stocker et partager les modèles versionnés | Hugging Face |
| Surveillance de prompts | Tracer les chaînes de prompts d'un LLM | Opik |
| Base NoSQL | Stocker les données brutes non structurées | MongoDB |
| Base de données vectorielle | Recherche sémantique pour le RAG | Qdrant |
| Cloud et calcul GPU | Entraîner et servir les modèles à l'échelle | AWS / SageMaker |
| Conteneurisation | Reproduire l'infra localement et en prod | Docker |
L'écosystème Python : reproductibilité dès la racine
Tout projet Python repose sur trois fondations : l'interpréteur, la gestion des dépendances et un exécuteur de tâches (task runner). Le livre fixe Python 3.11.8 comme version de référence, et c'est là que la reproductibilité commence vraiment.
pyenv : une version d'interpréteur par projet
Plutôt que d'installer plusieurs Python globaux qui s'écrasent mutuellement, pyenv permet de gérer plusieurs versions et d'en épingler une par dossier. Un simple fichier .python-version à la racine du dépôt suffit : pyenv détecte automatiquement la bonne version dès qu'on travaille dans ce répertoire.
pyenv install 3.11.8 # installe la version exacte
pyenv local 3.11.8 # cree le fichier .python-version
python --version # -> Python 3.11.8 (dans ce dossier) Poetry : figer l'arbre des dépendances
Poetry est le gestionnaire de dépendances et d'environnements virtuels retenu. Un gestionnaire de dépendances (dependency manager) permet de spécifier, installer et mettre à jour les bibliothèques externes dont un projet dépend. Poetry apporte deux garanties décisives.
D'abord, il épingle (pin) les versions. Le fichier pyproject.toml peut indiquer des plages (requests = "^2.25.1"), mais le fichier poetry.lock enregistre la version exacte réellement installée (requests = "2.25.1"), y compris les sous-dépendances non listées explicitement. C'est ce verrouillage qui élimine le fameux « ça marche sur ma machine ».
[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.25.1"
numpy = "^1.19.5"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api" Ensuite, Poetry crée un environnement virtuel (virtual environment) isolé. Si le projet A exige numpy == 1.19.5 et le projet B numpy == 1.26.0, les garder dans l'environnement global est impossible : l'un écraserait l'autre. L'isolation par projet supprime ce conflit.
Astuce
Les auteurs citent les alternatives et leurs compromis. Venv et Conda créent des environnements mais gèrent mal les dépendances (on retombe sur des requirements.txt moins puissants que les fichiers de verrouillage). Pipenv ressemble à Poetry mais est plus lent. Surtout, ils signalent uv, un remplaçant de Poetry écrit en Rust et bien plus rapide, jugé prometteur. Le principe reste le même quel que soit l'outil : épingler pour reproduire.
Poe the Poet : la façade des commandes
Dernier maillon, Poe the Poet est un greffon de Poetry qui centralise toutes les commandes CLI du projet directement dans pyproject.toml, plutôt que de les éparpiller dans des scripts shell ou un Makefile.
[tool.poe.tasks]
test = "pytest"
format = "black ."
start = "python main.py" L'intérêt dépasse le confort : une façade unique sur les commandes agit comme une documentation vivante et simplifie la collaboration. Tout le monde lance poetry poe test ou poetry poe run-digital-data-etl sans avoir à connaître les détails sous-jacents.
ZenML : l'orchestrateur, le pont entre ML et MLOps
C'est le cœur de la pile. ZenML se présente comme la passerelle entre la recherche exploratoire (les notebooks) et un environnement de production. Il s'attaque frontalement aux problèmes de réplication en production : difficultés de versionnage, reproduction des expériences, organisation de flux complexes, et fossé entre entraînement et déploiement.
Pipelines et steps : le motif universel des orchestrateurs
Un orchestrateur (orchestrator) automatise, planifie et coordonne les pipelines ML pour qu'ils s'exécutent dans le bon ordre, gèrent leurs dépendances, optimisent les ressources et tolèrent les pannes. ZenML applique un motif que l'on retrouve dans tous les orchestrateurs : une fonction de haut niveau, le pipeline, appelle plusieurs unités de travail, les steps (étapes). On bascule de l'un à l'autre par un simple décorateur.
from zenml import pipeline
from steps.etl import crawl_links, get_or_create_user
@pipeline
def digital_data_etl(user_full_name: str, links: list[str]) -> None:
user = get_or_create_user(user_full_name)
crawl_links(user=user, links=links) Chaque step est une fonction Python ordinaire, décorée par @step. Le point essentiel : pour bien s'intégrer à ZenML, il faut écrire du code modulaire, où chaque fonction fait une seule chose.
from loguru import logger
from typing_extensions import Annotated
from zenml import get_step_context, step
from llm_engineering.application import utils
from llm_engineering.domain.documents import UserDocument
@step
def get_or_create_user(
user_full_name: str,
) -> Annotated[UserDocument, "user"]:
logger.info(f"Getting or creating user: {user_full_name}")
first_name, last_name = utils.split_user_full_name(user_full_name)
user = UserDocument.get_or_create(
first_name=first_name, last_name=last_name
)
return user Une fois exécuté, l'enchaînement des steps forme un graphe orienté acyclique (directed acyclic graph, DAG), visualisable dans le tableau de bord ZenML, avec le statut de chaque run, ses logs agrégés et la pile (stack) utilisée.
À retenir
Une décision d'architecture lourde de conséquences : la granularité des steps. Chaque step s'exécutera comme une unité distincte, sur une machine distincte, une fois déployé dans le cloud. Découper trop fin multiplie les transferts ; trop gros, on perd en parallélisme et en observabilité. C'est un compromis à arbitrer projet par projet.
Découpler la logique de l'orchestrateur
Les auteurs insistent sur un choix de conception réutilisable : toute la logique métier vit dans un module llm_engineering, tandis que les dossiers steps et pipelines ne contiennent que le « collage » ZenML. Les steps importent ce dont ils ont besoin depuis llm_engineering comme on consomme un paquet ; les pipelines se contentent d'assembler les steps. Conséquence directe : on peut remplacer ZenML par un autre orchestrateur — ou réutiliser la même logique dans une API REST — sans toucher au code métier.
La feature « stack » : pas de verrouillage fournisseur
Plutôt qu'une énième plateforme ML, ZenML introduit la notion de stack (pile). Une stack relie ZenML à des infrastructures variées sans que le code Python ne change : un orchestrateur et moteur de calcul (SageMaker ou Vertex AI), un stockage distant (S3 ou Google Cloud Storage), un registre de conteneurs (Docker Registry ou ECR).
Code Python (steps + pipelines)
|
v
+-----------+
| ZenML | <- abstraction
+-----------+
/ |
v v v
SageMaker S3 ECR (stack AWS du LLM Twin)
Vertex AI GCS ... (stack alternative, code inchange) C'est précisément ce qui évite le verrouillage fournisseur (vendor lock-in) : passer d'AWS à GCP revient à changer la stack, pas le code. Le livre note qu'aucun des concurrents — Airflow, Prefect, Metaflow, Dagster, ou Kubeflow et Argo Workflows pour les adeptes de Kubernetes — n'offre cette abstraction, ce qui fait de ZenML, pour les auteurs, le meilleur compromis entre simplicité, fonctionnalités et coûts.
Artefacts et métadonnées : versionner les fichiers, pas que le code
Un artefact (artifact) est tout fichier produit pendant le cycle de vie ML : jeux de données, modèles, checkpoints, logs. ZenML transforme automatiquement chaque sortie de step en artefact versionné, partageable et enrichi de métadonnées. C'est ici que se concrétise le versionnage des données.
L'intérêt des métadonnées est de comprendre le contenu d'un artefact sans le télécharger : pour un jeu de données, sa taille, le ratio train/test, le nombre d'échantillons par catégorie. On les attache manuellement, ce qui permet de précalculer tout ce qui aide à la découverte des données dans l'entreprise.
from typing_extensions import Annotated
from zenml import ArtifactConfig, get_step_context, step
@step
def generate_instruction_dataset(
prompts: Annotated[dict, "prompts"],
) -> Annotated[
InstructTrainTestSplit,
ArtifactConfig(
name="instruct_datasets",
tags=["dataset", "instruct", "cleaned"],
),
]:
datasets = ... # generation du jeu de donnees
step_context = get_step_context()
step_context.add_output_metadata(
output_name="instruct_datasets",
metadata=_get_metadata_instruct_dataset(datasets),
)
return datasets Chaque artefact possède un identifiant unique (UUID) qui permet de retrouver et charger une version précise des données — la clé de la reproductibilité.
from zenml.client import Client
artifact = Client().get_artifact_version(
"8bba35c4-8ff9-4d8f-a039-08046efc9fdc"
)
loaded_artifact = artifact.load() Note
Une sortie de step doit être sérialisable pour devenir un artefact. ZenML sérialise la plupart des objets réductibles à des types primitifs, mais pas tout : les auteurs racontent avoir dû étendre le « materializer » de ZenML pour gérer les UUID utilisés comme identifiants. Anecdote instructive sur le fait qu'un orchestrateur transforme réellement vos sorties de fonctions en fichiers tracés.
Configurer un pipeline sans toucher au code
ZenML permet d'injecter une configuration au moment de l'exécution via un fichier YAML. Pour le LLM Twin, changer la personne dont on collecte les écrits revient à changer un fichier de config, jamais le code.
parameters:
user_full_name: Maxime Labonne
links:
- https://mlabonne.github.io/blog/posts/2024-07-29_Finetune_Llama31.html
- https://maximelabonne.substack.com/p/uncensor-any-llm-with-abliteration
# ... autres liens Cette approche découple les paramètres du code et trace les entrées de chaque pipeline, garantissant la reproductibilité de chaque run.
Suivre les expériences et stocker les modèles
L'orchestration assure le « comment » de l'exécution. Reste à savoir quel essai a gagné et où ranger le modèle final.
Comet ML : le suivi d'expériences
Un outil de suivi d'expériences (experiment tracking) journalise tout ce qui permet de comparer des essais : métriques d'entraînement et d'évaluation (loss, norme du gradient), hyperparamètres, mais aussi des métriques système prêtes à l'emploi (utilisation GPU, CPU, mémoire) pour repérer les goulots d'étranglement. Le livre utilise Comet ML dans sa version en ligne gratuite.
from comet_ml import Experiment
experiment = Experiment(project_name="llm-twin-training")
experiment.log_parameters({
"learning_rate": 2e-4,
"lora_rank": 16,
"epochs": 3,
})
for step, (train_loss, eval_loss) in enumerate(history):
experiment.log_metric("train_loss", train_loss, step=step)
experiment.log_metric("eval_loss", eval_loss, step=step) Les concurrents (W&B, MLflow, Neptune) offrent globalement les mêmes fonctionnalités ; les auteurs retiennent Comet pour sa simplicité et son interface intuitive.
Hugging Face : le registre de modèles
Un registre de modèles (model registry) est un dépôt centralisé qui gère les modèles tout au long de leur cycle de vie : il stocke chaque modèle avec ses métadonnées, son historique de versions et ses métriques, servant de source unique de vérité. Il s'intègre aux pipelines CI/CD pour le déploiement.
Le livre choisit Hugging Face non pour ses fonctionnalités brutes — ZenML, Comet et SageMaker proposent aussi un registre — mais pour son écosystème : partage immédiat des modèles affinés (les TwinLlama 3.1 8B et sa variante alignée par DPO) et intégration native avec Unsloth pour le fine-tuning et SageMaker pour l'inférence.
from transformers import AutoModelForCausalLM, AutoTokenizer
# Pousser un modele affine vers le registre Hugging Face
model.push_to_hub("mlabonne/TwinLlama-3.1-8B")
tokenizer.push_to_hub("mlabonne/TwinLlama-3.1-8B")
# Plus tard : recharger une version precise du registre
model = AutoModelForCausalLM.from_pretrained(
"mlabonne/TwinLlama-3.1-8B"
) La leçon généralisable : on choisit le registre qui s'intègre le mieux au reste de sa pile et à ses besoins de partage, pas le plus riche dans l'absolu.
Opik : surveiller les prompts
Les outils de logs classiques ne conviennent pas aux LLM. Pourquoi ? Parce qu'une interaction avec un LLM enchaîne plusieurs prompts d'entrée et sorties générées en une trace (trace), où chaque prompt dépend des précédents. Un log en texte plat est illisible ; il faut un tableau de bord spécialisé pour grouper et déboguer ces traces. Le livre utilise Opik, outil open source signé Comet, pour la surveillance de prompts (prompt monitoring), préféré à Langfuse, Galileo ou LangSmith, jugés plus lourds à mettre en œuvre, pour sa simplicité.
Les bases de données : NoSQL et vectorielle
Le LLM Twin manipule deux types de données, donc deux bases, toutes deux lancées localement via Docker.
MongoDB sert de base NoSQL pour stocker les données brutes collectées sur internet avant traitement. Comme on travaille avec du texte non structuré, la souplesse du schéma NoSQL convient parfaitement.
Qdrant est la base de données vectorielle (vector database) qui stocke les données une fois traitées et transformées en vecteurs (embeddings), pour la recherche sémantique au cœur du RAG (génération augmentée par récupération). Les auteurs auraient pu prendre n'importe quelle base vectorielle pour un MVP, mais ont retenu Qdrant pour son meilleur compromis entre débit (RPS), latence et temps d'indexation, et sa robustesse industrielle.
| Base | Type | Rôle dans le pipeline | Données stockées |
|---|---|---|---|
| MongoDB | NoSQL document | Collecte amont | Texte brut non structuré |
| Qdrant | Vectorielle | Récupération RAG aval | Embeddings + payload |
Astuce
Pour comparer en détail les bases vectorielles (Milvus, Redis, Weaviate, Pinecone, Chroma, pgvector...), le livre renvoie au comparatif de Superlinked, qui couvre licence, fonctionnalités, modèles d'embedding et frameworks supportés. Comparer ces bases en profondeur pourrait remplir un chapitre entier — d'où le renvoi.
Le cloud : AWS et SageMaker
L'entraînement et le service d'un LLM exigent des GPU coûteux : c'est là qu'intervient le cloud. Le livre choisit AWS, non par supériorité technique absolue (GCP et Azure offrent des services équivalents) mais parce que c'est le plus répandu et celui où les auteurs ont le plus d'expérience — un compromis assumé entre temps de développement, fonctionnalités et coûts.
SageMaker : le calcul d'entraînement et d'inférence
SageMaker est un service ML entièrement managé pour construire, entraîner et déployer des modèles à l'échelle, en masquant l'infrastructure sous-jacente. Le LLM Twin s'en sert pour affiner le modèle sur des clusters de GPU et le déployer comme API REST accessible en temps réel.
Le choix le plus instructif est celui de SageMaker plutôt que Bedrock. Voici le compromis fondamental que le livre met en lumière.
| Critère | Amazon Bedrock | AWS SageMaker |
|---|---|---|
| Modèle d'exécution | Serverless, aucune infra à gérer | Calcul provisionné à gérer |
| Personnalisation | Limitée aux modèles pré-entraînés | Contrôle complet du processus ML |
| Public visé | Développeurs sans expertise ML | Data scientists, experts ML |
| Tarification | Par appel d'API, prévisible | Pay-as-you-go, payé même au repos |
| Cas d'usage | Prototypage rapide | Ingénierie ML personnalisée |
Bedrock est un excellent choix pour prototyper vite, mais il masque justement les aspects d'ingénierie que ce livre veut enseigner. SageMaker offre la personnalisation nécessaire pour montrer tout le travail de déploiement.
Attention
SageMaker est une lame à double tranchant côté coûts : un endpoint déployé continue de facturer même inutilisé, contrairement au serverless de Bedrock. Il faut donc concevoir des systèmes d'autoscaling qui suppriment les ressources non utilisées. Les auteurs estiment le coût AWS du suivi pratique du livre entre 50 et 100 dollars, essentiellement liés aux tests SageMaker — d'où le conseil de configurer des alarmes de facturation.
Les auteurs notent enfin que SageMaker lui-même n'est pas totalement personnalisable : pour un contrôle complet à moindre coût, on descendrait sur EKS ou ECS (les services Kubernetes / conteneurs d'AWS), au prix d'une complexité bien supérieure. SageMaker représente donc l'équilibre entre contrôle et confort d'un service managé.
Docker et le compromis « tout maison » vs managé
Toute cette infrastructure — ZenML, MongoDB, Qdrant — se lance localement grâce à Docker (conteneurisation), en une commande : poetry poe local-infrastructure-up. La conteneurisation garantit que l'environnement reproduit localement est le même qu'en production, fermant la boucle de reproductibilité jusqu'au système d'exploitation.
En filigrane de tous ces choix se joue un arbitrage récurrent : faut-il héberger soi-même (open source, contrôle total, coût d'exploitation) ou recourir à des services managés (rapidité, fiabilité, facture)? Le livre adopte une voie pragmatique : services managés et options serverless gratuites quand c'est possible (Comet, Qdrant Cloud, ZenML serverless), Docker en local pour itérer sans frais, et calcul GPU payant uniquement quand l'entraînement l'exige. Le principe à retenir dépasse chaque outil : la pile doit servir les principes MLOps, pas l'inverse.
À retenir
- Les outils ne sont jamais une fin en soi : ils matérialisent trois principes MLOps — reproductibilité, automatisation et versionnage du code, des données et des modèles.
- L'écosystème Python pose les fondations : pyenv fige l'interpréteur, Poetry verrouille l'arbre des dépendances (
poetry.lock) pour tuer le « ça marche sur ma machine », et Poe the Poet centralise les commandes comme documentation vivante. - ZenML orchestre les pipelines via le motif
@pipeline/@step, transforme chaque sortie en artefact versionné doté de métadonnées, et son système de stack évite le verrouillage fournisseur en découplant le code de l'infrastructure. - Le suivi d'expériences (Comet ML) compare les essais, le registre de modèles (Hugging Face) sert de source de vérité partagée, et la surveillance de prompts (Opik) trace les chaînes spécifiques aux LLM.
- Deux bases pour deux besoins : MongoDB (NoSQL) pour le texte brut, Qdrant (vectorielle) pour la recherche sémantique du RAG.
- Côté cloud, le choix SageMaker plutôt que Bedrock illustre le compromis maître du livre : on sacrifie la simplicité serverless pour le contrôle d'ingénierie, en surveillant des coûts qui courent même au repos ; Docker reproduit toute la pile en local.