Redémarrage de services IA et orchestration
Redémarrer un service IA sans coupure : checkpoints, GPU et déploiements Kubernetes
Un service d'IA perd tout ce qui vit en mémoire GPU quand son processus s'arrête ; pour redémarrer sans dommage, on sauvegarde l'état (checkpoints), on arrête proprement sur SIGTERM, on remplace les instances progressivement ou par bascule (rolling update, blue/green) et on vérifie la reprise par des tests.
Objectifs
- Identifier ce qu'un redémarrage fait perdre à un entraînement et à un service d'inférence (poids chargés, état de l'optimiseur, sessions GPU, files de requêtes).
- Concevoir une sauvegarde d'état (checkpoint) et choisir sa fréquence.
- Configurer un arrêt propre et une mise à jour progressive dans Kubernetes : sondes, délai de grâce,
maxSurge,maxUnavailable. - Comparer rolling update et blue/green, en tenant compte du nombre limité de GPU.
- Écrire un plan de tests de reprise après redémarrage.
1. Ce qu'un redémarrage fait perdre à un système d'IA
La mémoire d'un GPU (VRAM) appartient au processus qui l'a allouée : quand le processus se termine, le pilote libère tout. Rien de ce qui s'y trouvait ne survit à un redémarrage, même d'un seul conteneur. On distingue deux cas.
1.1 Entraînement
L'état à sauvegarder pour reprendre exactement là où l'on s'était arrêté comprend :
- les poids du modèle (en PyTorch,
model.state_dict()) ; - l'état de l'optimiseur (
optimizer.state_dict(): par exemple les moments d'Adam), sans lequel la reprise ne suit plus la même trajectoire ; - l'état du planificateur de taux d'apprentissage, le numéro d'époque et de pas, la position dans les données ;
- les graines et états des générateurs aléatoires, si l'on veut une reprise reproductible.
On écrit tout dans un fichier de checkpoint, par exemple avec torch.save({...}, chemin), et on le recharge avec torch.load puis load_state_dict. Bonne pratique : écrire dans un fichier temporaire, puis le renommer (le renommage est atomique sur un même système de fichiers), pour ne jamais garder un checkpoint à moitié écrit.
1.2 Inférence (service en ligne)
- Poids chargés en VRAM : il faut les recharger, ce qui peut prendre de quelques secondes à plusieurs minutes selon la taille du modèle et la vitesse du stockage.
- Caches et sessions : contexte CUDA, noyaux compilés, cache de clés et valeurs d'un modèle de langage, conversations en cours.
- Requêtes en vol : celles déjà acceptées mais pas encore répondues.
Conséquence : un service d'IA n'est pas « prêt » dès que son processus démarre. Il faut un échauffement (warm-up : chargement, première inférence) avant de lui envoyer du trafic.
Pour reprendre un entraînement avec Adam exactement comme avant la coupure, que faut-il sauvegarder au minimum ?
2. Arrêt propre d'un conteneur dans Kubernetes
Quand Kubernetes supprime un pod, il le retire des points de terminaison du Service (il ne reçoit plus de nouveau trafic), exécute le crochet preStop s'il est défini, envoie SIGTERM au processus principal de chaque conteneur, attend au plus terminationGracePeriodSeconds (30 s par défaut), puis envoie SIGKILL. Le retrait des points de terminaison et l'envoi du signal se font en parallèle : un court preStop laisse le temps aux répartiteurs de charge de se mettre à jour.
Trois sondes décident de l'état du conteneur :
startupProbe: tant qu'elle n'a pas réussi, les autres sondes attendent ; utile pour un modèle long à charger ;readinessProbe: en cas d'échec, le pod reste vivant mais ne reçoit pas de trafic ; elle doit répondre « prêt » seulement après le warm-up ;livenessProbe: en cas d'échec répété, le kubelet redémarre le conteneur.
Extrait de Deployment (les valeurs sont un exemple à adapter) :
apiVersion: apps/v1
kind: Deployment
metadata:
name: traduction
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app: traduction
template:
metadata:
labels:
app: traduction
spec:
terminationGracePeriodSeconds: 60
containers:
- name: modele
image: registre.exemple/traduction:2.1
resources:
limits:
nvidia.com/gpu: 1
startupProbe:
httpGet: {path: /pret, port: 8080}
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /pret, port: 8080}
periodSeconds: 5
livenessProbe:
httpGet: {path: /vivant, port: 8080}
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["sleep", "5"]
Ici, la startupProbe laisse jusqu'à \(30 \times 10 = 300\) s pour charger le modèle. La ressource nvidia.com/gpu est fournie par le plugin de périphériques NVIDIA installé sur les nœuds.
Un pod d'inférence est démarré mais son modèle est encore en chargement. Quelle sonde doit l'empêcher de recevoir du trafic ?
3. Stratégies de haute disponibilité
3.1 Rolling update (mise à jour progressive)
Le Deployment remplace les pods par vagues. Deux paramètres :
maxSurge: nombre (ou pourcentage, arrondi au-dessus) de pods en plus dereplicasautorisés pendant la mise à jour ;maxUnavailable: nombre (ou pourcentage, arrondi au-dessous) de pods qui peuvent manquer. Les deux ne peuvent pas valoir 0 en même temps. Valeur par défaut : 25 % chacun.
Commandes : kubectl rollout restart deployment/traduction (redémarre tous les pods selon la stratégie, sans changer l'image), kubectl rollout status deployment/traduction (suivi), kubectl rollout undo deployment/traduction (retour à la révision précédente), kubectl rollout history deployment/traduction.
3.2 Blue/green (bascule)
On déploie une seconde version complète (« green ») à côté de l'actuelle (« blue »), on la teste, puis on bascule tout le trafic en modifiant le sélecteur du Service :
kubectl patch service traduction -p '{"spec":{"selector":{"app":"traduction","version":"green"}}}'
Retour arrière immédiat : on remet version: blue. Contrepartie : pendant la bascule, il faut deux fois les ressources, donc deux fois les GPU.
3.3 Redémarrer un nœud (machine) sans couper le service
kubectl cordon noeud-2: plus aucun nouveau pod n'y est placé ;kubectl drain noeud-2 --ignore-daemonsets: expulse les pods (en respectant leur délai de grâce et les PodDisruptionBudget) ; ajouter--delete-emptydir-datasi des pods utilisent un volumeemptyDir, dont le contenu sera perdu ;- redémarrer la machine (par exemple
systemctl reboot), vérifier les pilotes avecnvidia-smi; kubectl uncordon noeud-2: le nœud accepte de nouveau des pods.
Un PodDisruptionBudget (par exemple minAvailable: 3 pour l'application traduction) interdit au drain d'expulser des pods si cela faisait passer le service sous ce seuil.
flowchart LR
A[Nouvelle version] --> B{GPU libres pour une copie complete}
B -- oui --> C[Blue green]
B -- non --> D{Un GPU libre}
D -- oui --> E[Rolling maxSurge 1]
D -- non --> F[Rolling maxSurge 0 maxUnavailable 1]
C --> G[Tests de reprise]
E --> G
F --> G
Pourquoi un blue/green peut-il échouer sur un cluster de 4 GPU qui fait déjà tourner 4 pods d'inférence à 1 GPU chacun ?
4. Exemples résolus
Exemple 1 — Calculer les bornes d'un rolling update
Un Deployment a \(n = 10\) réplicas, maxSurge: 25% et maxUnavailable: 25%. Combien de pods au plus existent pendant la mise à jour, et combien au moins sont disponibles ?
maxSurge: 0.Exemple 2 — Choisir la fréquence des checkpoints
À Lomé, un entraînement doit durer 40 h. Une sauvegarde complète prend 3 min. L'équipe compare une sauvegarde toutes les 30 min et toutes les 2 h. Calcule, pour chaque choix, le temps total passé à sauvegarder et la perte maximale en cas de panne. Cherche d'abord.
Voir la solution
\[ \begin{align} \text{Toutes les 30 min : } & \frac{40 \times 60}{30} = 80 \text{ sauvegardes}, \quad 80 \times 3 = 240 \text{ min} = 4 \text{ h}, \quad \text{perte max} = 30 \text{ min} \\ \text{Toutes les 2 h : } & \frac{40}{2} = 20 \text{ sauvegardes}, \quad 20 \times 3 = 60 \text{ min} = 1 \text{ h}, \quad \text{perte max} = 2 \text{ h} \end{align} \]Sauvegarder toutes les 30 min coûte 3 h de plus, mais limite la perte à 30 min. Si les coupures sont fréquentes (plusieurs par entraînement), la sauvegarde fréquente devient rentable ; si elles sont rares, toutes les 2 h suffit. Pour réduire le coût, on peut sauvegarder de façon asynchrone (copie en mémoire, écriture en tâche de fond) ou ne garder que les derniers checkpoints.
Exemple 3 — Plan de tests de reprise
Après le redémarrage du nœud qui héberge le service de traduction, que vérifier avant de déclarer la reprise réussie ?
Voir la solution
- Infrastructure :
kubectl get nodes(nœud Ready),nvidia-smi(GPU visibles, pilote chargé),kubectl get pods -o wide(pods Running, compteur RESTARTS stable). - Disponibilité :
kubectl rollout statusterminé ; tous les pods passent la readinessProbe. - Exactitude : un jeu de référence de phrases (par exemple 50 phrases en français et leur traduction attendue) produit les mêmes sorties qu'avant le redémarrage, à tolérance près ; on vérifie aussi la version du modèle exposée par le service.
- Performance : la latence (par exemple le 95e centile) revient au niveau habituel après le warm-up.
- Entraînement : la reprise depuis le checkpoint repart au bon pas, et la perte (loss) continue la courbe au lieu de remonter brutalement.
Ces tests s'automatisent et se rejouent volontairement (redémarrage provoqué en préproduction) : on ne découvre pas en panne réelle qu'un test manquait.
5. Erreurs fréquentes
- Ne sauvegarder que les poids : la reprise de l'entraînement n'est plus fidèle (optimiseur et position perdus).
- Écrire le checkpoint dans le système de fichiers du conteneur ou un
emptyDir: il disparaît avec le pod. Il faut un volume persistant ou un stockage objet. - Ignorer SIGTERM dans le serveur d'inférence : les requêtes en vol sont coupées au bout du délai de grâce.
- Déclarer « prêt » avant le chargement du modèle : les premières requêtes échouent ou sont très lentes.
- Prévoir un
maxSurgesans GPU disponibles : la mise à jour se bloque avec des pods en attente. - Confondre
kubectl rollout restartetkubectl delete poden masse : le premier respecte la stratégie de mise à jour, le second peut supprimer tous les pods d'un coup.
6. Récapitulatif
- La mémoire GPU ne survit pas à l'arrêt du processus : on sauvegarde poids, optimiseur, position et graines dans des checkpoints écrits de façon atomique sur un stockage persistant.
- Arrêt propre dans Kubernetes : retrait du Service,
preStop, SIGTERM, délai de grâce (30 s par défaut), SIGKILL. startupProbepour le chargement,readinessProbepour le trafic,livenessProbepour la relance.- Rolling update (
maxSurgearrondi au-dessus,maxUnavailableau-dessous) ou blue/green (double capacité) ;cordon,drain,uncordonpour redémarrer un nœud. - Tests de reprise : infrastructure, disponibilité, exactitude, performance, continuité de l'entraînement.
Teste tes connaissances sur ce cours
Crée ton compte gratuitement pour accéder aux quiz associés et suivre ta progression.
