# Comment transformer un point de contrôle de modèle en application fonctionnelle?

Date: 2026-10-04 · Author: Robert Scales · https://guidetoartificialintelligence.com/fr/apprendre/du-point-de-controle-a-application/

**Answer:** Emballez les poids dans un format sûr avec une configuration, une version et une empreinte, vérifiez qu’ils donnent les mêmes nombres que le point de contrôle d’entraînement, puis enveloppez le tout dans un petit serveur avec des limites d’entrée et une vérification d’état, et mesurez la latence sous charge. Dans une exécution mesurée, un tout petit modèle a servi six requêtes par seconde et, avec 16 personnes en même temps, chacune a attendu 2,6 secondes en valeur médiane.

Un point de contrôle est un fichier de nombres qui n’a de sens qu’avec le code et les réglages exacts qui l’ont produit. Une application est quelque chose qu’une personne peut utiliser un mauvais jour sans lire votre carnet de notes. L’écart entre les deux est mince pour un petit modèle, et il relève surtout de soins logiciels ordinaires.

Ce projet prend [le tout petit modèle du projet six](/fr/apprendre/petit-modele-langage-poids-aleatoires/) et lui fait traverser cet écart. Le script `14_checkpoint_to_app.py` fait chaque étape, puis arrête son serveur, en n’utilisant que la bibliothèque standard de Python, PyTorch et safetensors, à l’adresse locale de cet ordinateur (127.0.0.1). Il a pris 26 secondes sur un Apple M5 Max.

## Étape 1 : l’emballer

Un point de contrôle créé avec `torch.save` utilise le format pickle de Python, et [charger un pickle peut exécuter du code arbitraire](https://github.com/huggingface/safetensors). safetensors ne stocke que les tenseurs. Le script y enregistre les poids et écrit une petite description à côté.

```python
import hashlib, json
from pathlib import Path
from safetensors.torch import load_file, save_file

def sha(path):
    return hashlib.sha256(Path(path).read_bytes()).hexdigest()

def package(state, cfg, chars, folder):
    Path(folder).mkdir(parents=True, exist_ok=True)
    state = {k: v.clone() for k, v in state.items() if k != "out.weight"}   # partagé avec tok.weight
    save_file(state, f"{folder}/model.safetensors")
    meta = {"name": "mini-gpt-anne", "version": "1.0.0", "cfg": cfg, "chars": chars,
            "limits": "Écrit du charabia façon roman. Pas pour les faits.",
            "sha256": sha(f"{folder}/model.safetensors")}
    Path(f"{folder}/model.json").write_text(json.dumps(meta, ensure_ascii=False))
```

La ligne `limits` compte. Quiconque reprendra ce modèle plus tard doit apprendre par le fichier lui-même à quoi il ne doit pas servir. Si vous prévoyez de le partager, [la licence et la fiche du modèle ont leurs propres étapes](/fr/apprendre/publier-modele-langage-licence/).

La taille a à peine changé : 3,29 Mo pour le point de contrôle d’entraînement et 3,28 Mo une fois emballé. Les points de contrôle plus gros qui stockent aussi l’état de l’optimiseur rétrécissent davantage, puisque l’application n’a besoin que des poids.

## Étape 2 : prouver que c’est le même modèle

Trois tests, tous courts. Gardez-les avec l’application et exécutez-les à chaque changement.

| Test | Méthode | Mesuré ici |
|---|---|---|
| Mêmes nombres | Comparer les sorties du point de contrôle et du modèle emballé sur une entrée aléatoire | Plus grand écart : 0,0 |
| Même texte | Même prompt et même graine, deux fois | Identique |
| Refus des dommages | Inverser un bit dans une copie du fichier, puis charger | Refusé : l’empreinte ne correspond pas |

## Étape 3 : l’envelopper dans un petit serveur

Le serveur a trois routes : `/` pour une petite page Web, `/health` pour vérifier l’état et `/generate` pour le modèle. L’essentiel de son code ne concerne pas du tout le modèle. Il concerne ce qu’une requête a le droit de demander.

```python
MAX_CHARS, MAX_PROMPT = 400, 200

def do_POST(self):
    length = int(self.headers.get("Content-Length", 0))
    if self.path != "/generate":
        return self.send(404, {"error": "introuvable"})
    if length > 2000:
        return self.send(413, {"error": "requête trop volumineuse"})
    try:
        req = json.loads(self.rfile.read(length))
        prompt, n, seed = str(req.get("prompt", "")), int(req.get("chars", 100)), int(req.get("seed", 1))
    except (ValueError, TypeError):
        return self.send(400, {"error": "envoyez du JSON avec prompt et chars"})
    if not prompt or len(prompt) > MAX_PROMPT or not 1 <= n <= MAX_CHARS:
        return self.send(422, {"error": f"prompt de 1 à {MAX_PROMPT} caractères, chars de 1 à {MAX_CHARS}"})
    with LOCK:                               # une génération à la fois; les autres attendent
        text = generate(model, meta, prompt, n, seed)
    self.send(200, {"text": text, "version": meta["version"]})
```

Sans les limites, une demande de 99 999 caractères monopoliserait la machine aux dépens de tout le monde. Résultats des tests sur le vrai serveur : une bonne requête a renvoyé 200, un prompt vide 422, trop de caractères 422 et un chemin inconnu 404. La réponse passée par le serveur correspondait exactement au texte enregistré plus tôt, ce qui prouve aussi que toute la chaîne fonctionne.

La documentation de Python prévient que [`http.server` n’est pas recommandé en production](https://docs.python.org/3/library/http.server.html), puisqu’il n’applique que des contrôles de sécurité de base. C’est un bon outil d’apprentissage, sur votre propre machine. Ne l’exposez pas à Internet.

## Étape 4 : le mesurer avec plusieurs personnes à la fois

Une seule personne ne vous apprend pas grand-chose. Mesuré ici sur le processeur, avec 100 caractères générés par requête. p50 est l’attente médiane : la moitié des requêtes ont été plus rapides. p95 est l’attente subie par la plus lente sur vingt.

| Personnes en même temps | Requêtes | Attente médiane | Attente p95 | Requêtes par seconde | Erreurs |
|---|---|---|---|---|---|
| 1 | 20 | 0,164 s | 0,169 s | 6,17 | 0 |
| 4 | 40 | 0,707 s | 0,745 s | 5,60 | 0 |
| 16 | 80 | 2,642 s | 2,813 s | 6,09 | 0 |

Le débit reste stable à environ six par seconde et l’attente croît avec le nombre de personnes. Cela vient directement de la ligne `LOCK` : les requêtes font la file et rien n’échoue. C’est la première forme normale d’un modèle en service. Vos options sont de regrouper plusieurs requêtes en lot, d’exécuter plusieurs copies ou de générer moins de caractères. Le processus a culminé à 250 Mo. Pour un vrai modèle de langage, [un serveur spécialisé comme vLLM ou llama.cpp](/fr/apprendre/serveur-ia-local-vllm-llama-cpp/) s’occupe des lots et de la diffusion en continu, et ces quatre mêmes étapes s’appliquent toujours.

> **À essayer :** retirez le `LOCK` du serveur, relancez le test de charge et comparez les requêtes par seconde et les erreurs au tableau. Décidez ensuite si vous le garderiez et notez pourquoi.

## Que manque-t-il encore avant que de vraies personnes l’utilisent?

Ce projet s’arrête à une démonstration locale, et rien de ce qui suit n’a été testé. Avant de laisser quiconque de l’extérieur utiliser une application de ce genre, il vous faut :

| Domaine | Quoi ajouter |
|---|---|
| Accès | Une connexion ou une clé, des limites par personne et un serveur de production avec connexions chiffrées |
| Vie privée | Une décision sur ce que vous journalisez. Les prompts peuvent contenir des renseignements personnels : gardez-en le moins possible et, au Québec, vérifiez vos obligations en vertu de la Loi 25 |
| Sécurité | Un énoncé clair de la raison d’être du modèle. Des tests avec des entrées hostiles et bizarres. Une personne à joindre |
| Mises à jour | La version dans chaque réponse, un moyen de revenir à la version précédente, des tests à réussir d’abord |
| Surveillance | La latence, les erreurs et un échantillon humain régulier des sorties. Un score d’entraînement vieillit à mesure que l’usage réel dérive |
| Honnêteté | Dire aux gens que le texte vient d’un modèle et qu’il peut se tromper |

Le modèle est la plus petite partie de ce tableau. C’est le constat habituel à la fin de ce genre de projet : l’entraînement est l’étape visible, et la fiabilité de l’application repose sur tout le travail ordinaire qui l’entoure. La série se termine ici, et [la feuille de route plus large](/fr/apprendre/batir-son-modele-de-langage-feuille-de-route/) peut vous aider à choisir la suite.

## Sources

- safetensors README: https://github.com/huggingface/safetensors
- http.server: HTTP servers: https://docs.python.org/3/library/http.server.html
- nanoGPT (README): https://github.com/karpathy/nanoGPT

---

Tiré du Guide de l’intelligence artificielle, par Robert Scales, publié par Holistic Hybrid inc. Partage et adaptation permis selon la licence CC BY-NC-SA 4.0 (https://creativecommons.org/licenses/by-nc-sa/4.0/) : citez le guide, mettez un lien vers https://guidetoartificialintelligence.com/fr/apprendre/du-point-de-controle-a-application/, pas d’usage commercial sans permission, et gardez la même licence.
