Réponse courte
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.
Question de suivi Que manque-t-il encore avant que de vraies personnes l’utilisent?
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 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. safetensors ne stocke que les tenseurs. Le script y enregistre les poids et écrit une petite description à côté.
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.
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.
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, 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 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 peut vous aider à choisir la suite.
Téléchargez le code de cette série (zip), avec tous les scripts et les résultats enregistrés.
Sources
- safetensors README — Hugging Face (GitHub), 2 novembre 2026
- http.server: HTTP servers — Python
- nanoGPT (README) — Andrej Karpathy (GitHub), 12 novembre 2025
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 : 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.