Skip to main content
Cette page explique en détail comment W&B Sweeps gère les signaux système et les codes de sortie des processus. Utilisez ces informations pour exécuter des sweeps de manière fiable dans des environnements préemptibles tels que SLURM, EC2 Spot ou les VM préemptibles de Google Cloud. Les sections suivantes décrivent comment interrompre proprement des runs depuis le clavier et apportent des précisions pour vous aider à comprendre et à anticiper la façon dont les runs sont remis en file d’attente. Cette page s’adresse aux utilisateurs qui exécutent des sweeps sur une infrastructure préemptible ou qui ont besoin d’un contrôle précis sur le cycle de vie et le nettoyage des runs. Pour en savoir plus sur la façon dont W&B remet les runs en file d’attente lorsqu’ils sont préemptés, consultez Reprendre les runs de sweep préemptibles.

Statut de sortie et signaux

W&B utilise le statut de sortie du processus d’entraînement pour déterminer si un run est remis en file d’attente et comment son état est enregistré. Contrat des codes de sortie :
  • Code de sortie 0 : W&B considère que le run s’est terminé avec succès et ne le remet pas en file d’attente.
  • Code de sortie non nul : W&B considère que le run a échoué ou a été préempté. Lorsque vous utilisez mark_preempting(), W&B remet le run en file d’attente afin qu’un autre agent (ou le même agent après redémarrage) puisse le reprendre.
Ce comportement s’applique que le processus se termine depuis un gestionnaire de signal, à la suite d’une exception ou par un appel explicite à sys.exit(). Dans les environnements préemptibles ou en cluster, il est essentiel de bien comprendre ce contrat et de s’appuyer dessus. Lorsque le processus se termine en raison d’un signal interceptable, votre gestionnaire peut s’exécuter, appeler wandb.run.mark_preempting() si vous souhaitez que le run soit remis en file d’attente, effectuer un nettoyage (par exemple, enregistrer un point de contrôle), puis se terminer avec un code non nul. Par convention, on utilise généralement sys.exit(128 + signum) pour une terminaison par signal. W&B enregistre ce code de sortie, et les mêmes règles de remise en file d’attente s’appliquent. Lorsque le noyau du système d’exploitation tue le processus avec SIGKILL, le processus ne peut pas exécuter les hooks de sortie : W&B n’écrit donc pas de synthèse finale, et le run peut apparaître comme planté ou tué. L’agent démarre néanmoins le run suivant.

Runs inactifs et délais d’expiration côté serveur

W&B détermine l’état d’un run à partir des codes de sortie, mais aussi de l’activité du run. Si un run ne se termine pas et ne publie aucune nouvelle métrique pendant environ 5 minutes, W&B le marque comme planté. Cela peut se produire lorsque le processus d’entraînement ne répond plus, cesse de journaliser ou s’arrête sans sortie propre (par exemple, après l’envoi d’un SIGKILL). Consignez des métriques à intervalles réguliers ou terminez le processus avec un code de sortie défini, afin que l’état du run reflète fidèlement ce qui s’est réellement produit.

Signaux interceptables et préemption

Dans les environnements préemptibles, la plupart des signaux sont interceptables : votre script d’entraînement peut donc les intercepter et s’arrêter proprement. Vous pouvez enregistrer des gestionnaires de signaux personnalisés dans votre script d’entraînement. Lorsque le système envoie un signal interceptable, votre gestionnaire s’exécute. W&B conserve les métriques qu’il a déjà reçues. L’agent détecte la fin du processus et lance le run suivant. Bonnes pratiques :
  • Enregistrez les gestionnaires le plus tôt possible (par exemple, avant d’entrer dans la boucle d’entraînement principale).
  • Dans le gestionnaire, appelez wandb.run.mark_preempting() si vous souhaitez que le run soit remis en file d’attente après la préemption, effectuez le nettoyage (par exemple, enregistrez un point de contrôle), puis quittez avec un code de sortie non nul.
L’exemple suivant enregistre des gestionnaires pour SIGUSR1 (un signal de préemption courant sur les clusters) et SIGTERM. Il laisse SIGINT disponible pour une utilisation interactive (par exemple, une annulation manuelle depuis le terminal). Le gestionnaire appelle wandb.run.mark_preempting() et quitte avec le code 128 + signum :

SIGKILL (non interceptable)

SIGKILL provient du noyau du système d’exploitation et ne peut être ni intercepté ni ignoré. Le processus s’arrête immédiatement, sans pouvoir exécuter de gestionnaires ni de callbacks atexit. W&B ne peut pas écrire la synthèse finale du run. L’agent parvient malgré tout à se rétablir et poursuit le sweep, mais les données de ce run restent incomplètes. N’utilisez SIGKILL qu’en dernier recours. Si vous avez besoin d’un arrêt propre, privilégiez SIGTERM ou SIGINT.

Transfert des signaux de l’agent vers le processus enfant

Lorsque vous utilisez la CLI wandb agent, l’agent exécute votre script d’entraînement en tant que processus enfant. Lorsque vous interrompez l’agent (par exemple avec Ctrl+C, ou lorsqu’un scheduler envoie SIGTERM au job), le processus enfant (le processus d’entraînement) ne reçoit pas le signal par défaut. Le script d’entraînement ne peut alors ni exécuter son gestionnaire ni appeler mark_preempting(). Pour plus d’informations, voir le ticket GitHub wandb n° 3667. Pour permettre au processus enfant de s’arrêter proprement et d’appeler wandb.run.mark_preempting() dans un gestionnaire, exécutez l’agent CLI avec l’option --forward-signals :
W&B ne prend pas en charge le transfert de signaux pour wandb.agent() dans l’API Python. Dans ce cas, votre fonction d’entraînement s’exécute dans un thread et non dans un processus enfant distinct : le transfert ne fonctionne donc pas de la même manière. Lorsque l’agent CLI reçoit SIGINT ou SIGTERM alors que le transfert est activé, il relaie le signal au processus enfant. Le gestionnaire de votre script d’entraînement peut alors s’exécuter, appeler wandb.run.mark_preempting() et, si nécessaire, wandb.finish() avec un code de sortie non nul, puis se terminer avec un code non nul. Si vous appuyez deux fois sur Ctrl+C dans le processus de l’agent, celui-ci reçoit SIGTERM par défaut. Avec --forward-signals, l’agent peut transférer SIGINT au processus enfant afin que votre gestionnaire s’exécute. Pour plus d’informations, consultez la référence CLI de wandb agent.

Clusters préemptibles comme SLURM

Cette section explique comment configurer les sweeps pour que les runs survivent à la préemption sur des clusters tels que SLURM, EC2 Spot ou les VM préemptibles de Google Cloud. En cas de préemption, le processus d’entraînement doit recevoir le signal, marquer le run comme en cours de préemption, puis se terminer avec un code non nul pour que W&B remette le run en file d’attente. Un nouvel agent (ou le même agent, une fois le job remis en file d’attente) peut alors reprendre le run. Assurez-vous que le processus d’entraînement reçoit le signal :
  • Lorsque le scheduler envoie le signal à l’agent : exécutez l’agent avec wandb agent --forward-signals pour que, lorsque le scheduler (ou l’utilisateur) envoie un signal à l’agent, celui-ci le transmette au processus enfant. Le gestionnaire du processus enfant peut alors appeler wandb.run.mark_preempting(), wandb.finish(exit_code=...) avec un code non nul, puis sys.exit(128 + signum) (ou un autre code de sortie non nul).
  • Lorsque le scheduler envoie le signal au script de lancement (et non directement à l’agent) : faites en sorte que le script de lancement transmette le signal de préemption directement au processus d’entraînement. Par exemple, le script d’entraînement écrit son identifiant de processus (PID) dans un fichier. Le script de lancement intercepte le signal du cluster (par exemple, SIGUSR1) et exécute kill -SIGUSR1 $(cat $PID_FILE) pour déclencher le gestionnaire du processus d’entraînement.
Dans le script d’entraînement : enregistrez un gestionnaire pour le signal utilisé par votre cluster (par exemple, SIGTERM ou SIGUSR1). Dans ce gestionnaire, appelez wandb.run.mark_preempting() si un run est actif, puis terminez le run avec un code de sortie non nul et appelez sys.exit(128 + signum) (ou un autre code non nul) pour que W&B remette le run en file d’attente. Pour savoir dans quels cas W&B remet les runs en file d’attente et comment cela interagit avec mark_preempting(), voir Reprendre les runs Sweeps préemptibles. État du sweep : exécutez wandb sweep entity/project/sweep_ID --resume avant de démarrer l’agent, afin que le sweep passe en mode reprise et distribue les runs remis en file d’attente. Coordination multi-agents : lorsque de nombreux agents s’exécutent simultanément (par exemple avec des array jobs SLURM), ils peuvent se disputer le même run préempté. Il s’agit d’une limitation connue. Pour la contourner, échelonnez le démarrage des agents ou utilisez des mécanismes de coordination externes, tels que des verrous. Pour les jobs SLURM multi-GPU dans lesquels un seul processus doit appeler wandb.agent(), voir Comment exécuter des sweeps sur SLURM ?.

wandb sweep --cancel

Cette section décrit comment la commande --cancel interagit avec les signaux et les processus enfants, car l’annulation ne se comporte pas de la même manière que l’envoi direct d’un signal du système d’exploitation. L’annulation d’un sweep passe par l’API W&B, et non par un signal du système d’exploitation. Exécutez une commande telle que wandb sweep --cancel entity/project/sweep_ID. Le serveur demande à l’agent de s’arrêter ; l’agent met alors fin aux processus enfants en cours d’exécution, puis s’arrête. Un court délai (de l’ordre de l’intervalle d’interrogation de l’API par l’agent) peut s’écouler avant que l’annulation ne prenne effet. L’annulation envoie SIGKILL aux runs. Les processus enfants n’ont donc pas la possibilité d’exécuter les gestionnaires de signaux définis par l’utilisateur. Il en va de même lorsque vous utilisez la commande Cancel dans l’interface Sweeps. Utilisez --cancel lorsque vous souhaitez arrêter l’ensemble du sweep et le marquer comme annulé. Pour arrêter proprement le run actuel, envoyez-lui un signal interceptable (ou utilisez --forward-signals avec l’agent CLI et envoyez le signal à l’agent). Pour terminer proprement un sweep, utilisez wandb sweep --stop plutôt que --cancel. Pour plus d’informations sur les options de mise en pause, de reprise, d’arrêt et d’annulation, voir Gérer les sweeps.

Signaux envoyés à l’agent ou signaux envoyés au run

Faire la distinction entre l’envoi d’un signal à l’agent et l’envoi d’un signal au run d’entraînement vous permet d’éviter les processus orphelins et les comportements inattendus. Si vous envoyez un signal au processus de l’agent (et non au processus d’entraînement enfant), l’agent peut s’arrêter alors que le processus enfant continue de s’exécuter en tant qu’orphelin. Ce processus orphelin peut continuer à écrire dans votre terminal, et le shell peut ne pas afficher de nouvelle invite tant que vous n’appuyez pas sur Entrée. Si vous n’utilisez pas --forward-signals avec l’agent CLI, l’arrêt de l’agent ne garantit pas celui du processus d’entraînement enfant. Pour vérifier que l’agent s’est bien arrêté, utilisez une commande du système d’exploitation comme ps -p [AGENT-PID] ou pgrep -f "wandb agent" plutôt que de vous fier à l’apparition de l’invite.

Référence : mark_preempting() et état final du run

Le tableau suivant résume comment l’état du run varie selon le moment où vous appelez mark_preempting() et la façon dont le processus se termine. Il part du principe que vous utilisez la CLI wandb agent et que votre programme d’entraînement s’exécute comme sous-processus. Si vous appelez mark_preempting() uniquement dans un gestionnaire de signal, vous ne couvrez pas les cas où le gestionnaire ne s’exécute jamais, comme avec SIGKILL. Si vous appelez systématiquement mark_preempting() immédiatement après wandb.init(), W&B peut traiter tout échec comme une préemption et risque de remettre le run en file d’attente à répétition, y compris en cas de bogue ou de configuration incorrecte. Dans les environnements disposant d’un signal de préemption bien défini, l’approche habituelle consiste à utiliser un gestionnaire de signal qui appelle mark_preempting() et se termine avec un code non nul, plutôt qu’un appel inconditionnel après init().
Dernière modification le 30 septembre 2026