Notre contrôle de santé a redémarré huit fois par jour un serveur de modèles pourtant sain
Opérations
Le 22 juillet, un chien de garde a redémarré huit fois notre serveur de modèles local. Aucun de ces redémarrages n'était dû à un plantage. Le contrôle censé maintenir le modèle actif était précisément ce qui le faisait tomber.
Ce qui s'est passé
Le serveur de modèles exécutait vLLM sur une machine NVIDIA GB10 et produisait environ 8 tokens par seconde pour une seule requête. Toutes les cinq minutes, un chien de garde lui envoyait une vraie requête de chat et attendait la réponse jusqu'à 45 secondes. Cinq contrôles échoués d'affilée signifiaient un serveur bloqué, et le chien de garde le redémarrait. Un redémarrage privait le modèle de service pendant plusieurs minutes, le temps de recharger environ 19 Go de poids.
Ce jour-là, un traitement par lots maintenait deux à quatre requêtes actives sur le serveur pendant plusieurs minutes à la fois. La requête du chien de garde attendait dans la file derrière elles, manquait de temps et était comptée comme un échec. Après cinq contrôles de ce type, un serveur sain a été redémarré. Nous avions un fichier d'indicateur « occupé » que le chien de garde respectait, mais le traitement par lots ne le définissait jamais.
Comment un faux redémarrage a entraîné le suivant
Chaque redémarrage faisait échouer les requêtes en cours avec une erreur 500 (« cannot schedule new futures after shutdown »). Un second chien de garde comptait les erreurs 500 et redémarrait le serveur lorsqu'il y en avait trop, et plus il voyait d'erreurs, plus il agissait vite. Ainsi, chaque faux redémarrage faisait survenir le suivant plus tôt. Au total, quatre chiens de garde distincts pouvaient redémarrer ce serveur, sur des minuteurs de 5, 10, 15 et 30 minutes.
La règle que nous en avons tirée
Un contrôle de santé ne doit pas utiliser ce qu'il surveille. La ressource rare d'un serveur de modèles est ses emplacements pour les requêtes en cours. Un contrôle qui prend un de ces emplacements, ou qui attend d'en obtenir un, fait partie de la charge qu'il est censé évaluer. Vérifiez à moindre coût, et ne redémarrez que lorsque vous pouvez prouver que le serveur est bloqué.
Le contrôle qui l'a remplacé
vLLM publie ses propres compteurs sur /metrics. Le nouveau contrôle les lit d'abord :
- Requêtes en cours et moteur en progression : sain et occupé. Aucune requête de test n'est envoyée.
- Requêtes en cours et aucune progression entre deux lectures : bloqué. C'est le cas qui provoque encore un redémarrage.
- Rien en cours : inactif. Ce n'est qu'alors que le contrôle demande une réponse courte, que le moteur inactif fournit immédiatement.
La première version comptait les tokens générés comme une progression. Elle a réussi huit tests, et lors de sa première exécution réelle, elle a trouvé le serveur occupé et sain : 2 requêtes en cours, 107 nouveaux tokens entre les deux lectures.
Ce que nous avons changé depuis
- Des étapes de moteur, pas des jetons de sortie. Pendant que vLLM lit une longue invite, il ne produit aucun jeton de sortie pendant un certain temps mais continue d'exécuter des étapes. Compter les étapes (le nombre de vllm:iteration_tokens_total) évite qu'une longue invite ressemble à un serveur bloqué.
- Deux attentes avant « bloqué », afin qu'une courte pause ne soit pas considérée comme un échec.
- Un redémarrage entre les deux lectures est détecté par le temps de démarrage du processus dans les mêmes métriques ; la vérification demande alors une réponse au lieu de comparer des compteurs de deux processus différents.
- Un proxy est vérifié uniquement via /models. Derrière un proxy à moindre connexion, chaque lecture de /metrics peut provenir d'un backend différent, comme nous l'avons constaté lorsque nous avons exécuté une copie du modèle sur chacune des deux machines. Les backends sont vérifiés directement.
La différence est facile à voir. Au moment où nous écrivions cette note, nos deux serveurs de modèles traitaient chacun 12 requêtes, leur limite. Une requête de test aurait attendu dans la file d'attente. La nouvelle vérification a signalé les deux serveurs comme occupés et sains, à 126 et 101 jetons par seconde, sans en envoyer une.
Factory Floor 1.0.2 inclut cette vérification dans models/llm-health.py, avec des tests pour les serveurs inactifs, occupés, bloqués et redémarrés, un serveur sans métriques, un proxy et un backend hors service.