Concevoir des jobs GPU résilients de longue durée

Ce tutoriel explique comment concevoir et déployer des jobs Cloud Run résilients avec GPU pouvant s'exécuter pendant 12 heures.

Bien que Cloud Run accepte des délais avant expiration des tâches prolongés, il ne propose pas de contrat de niveau de service pour une exécution ininterrompue. Étant donné que les tâches compatibles avec les GPU ne sont pas migrées à chaud, la maintenance hebdomadaire de routine les interrompt en cours d'exécution. Par conséquent, toute tâche prolongée doit être interrompue au moins une fois. Pour vous assurer que votre job se termine correctement sans gaspiller de ressources de calcul, concevez votre application de manière à ce qu'elle crée des points de contrôle et reprenne son exécution, plutôt que de vous fier à une seule exécution ininterrompue.

Ce tutoriel vous explique comment implémenter des points de contrôle, gérer les signaux SIGTERM de la plate-forme et configurer le nombre maximal de nouvelles tentatives.

Objectifs

Dans ce tutoriel, vous allez :

  • Préparez une application Python qui implémente les points de contrôle et gère les signaux SIGTERM.
  • Créez un bucket Cloud Storage pour conserver les points de contrôle.
  • Créer et déployer un job Cloud Run avec un GPU activé, un délai avant expiration de 12 heures pour les tâches et une règle de nouvelle tentative configurée.
  • Exécuter le job et comprendre comment surveiller les événements de préemption dans les journaux.

Coûts

Dans ce document, vous utilisez les composants facturables suivants de Google Cloud :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût.

Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai sans frais.

Avant de commencer

  1. Connectez-vous à votre compte Google Cloud . Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits sans frais pour exécuter, tester et déployer des charges de travail.
  2. Installez la Google Cloud CLI.

  3. Si vous utilisez un fournisseur d'identité (IdP) externe, vous devez d'abord vous connecter à la gcloud CLI avec votre identité fédérée.

  4. Pour initialiser la gcloud CLI, exécutez la commande suivante :

    gcloud init
  5. Créez ou sélectionnez un projet Google Cloud .

    Rôles requis pour sélectionner ou créer un projet

    • Sélectionner un projet : la sélection d'un projet ne nécessite pas de rôle IAM spécifique. Vous pouvez sélectionner n'importe quel projet pour lequel un rôle vous a été attribué.
    • Créer un projet : pour créer un projet, vous devez disposer du rôle "Créateur de projet" (roles/resourcemanager.projectCreator), qui contient l'autorisation resourcemanager.projects.create. Découvrez comment attribuer des rôles.
    • Créez un projet Google Cloud  :

      gcloud projects create PROJECT_ID

      Remplacez PROJECT_ID par le nom du projet Google Cloud que vous créez.

    • Sélectionnez le projet Google Cloud que vous avez créé :

      gcloud config set project PROJECT_ID

      Remplacez PROJECT_ID par le nom de votre projet Google Cloud .

  6. Vérifiez que la facturation est activée pour votre projet Google Cloud .

  7. Installez la Google Cloud CLI.

  8. Si vous utilisez un fournisseur d'identité (IdP) externe, vous devez d'abord vous connecter à la gcloud CLI avec votre identité fédérée.

  9. Pour initialiser la gcloud CLI, exécutez la commande suivante :

    gcloud init
  10. Créez ou sélectionnez un projet Google Cloud .

    Rôles requis pour sélectionner ou créer un projet

    • Sélectionner un projet : la sélection d'un projet ne nécessite pas de rôle IAM spécifique. Vous pouvez sélectionner n'importe quel projet pour lequel un rôle vous a été attribué.
    • Créer un projet : pour créer un projet, vous devez disposer du rôle "Créateur de projet" (roles/resourcemanager.projectCreator), qui contient l'autorisation resourcemanager.projects.create. Découvrez comment attribuer des rôles.
    • Créez un projet Google Cloud  :

      gcloud projects create PROJECT_ID

      Remplacez PROJECT_ID par le nom du projet Google Cloud que vous créez.

    • Sélectionnez le projet Google Cloud que vous avez créé :

      gcloud config set project PROJECT_ID

      Remplacez PROJECT_ID par le nom de votre projet Google Cloud .

  11. Vérifiez que la facturation est activée pour votre projet Google Cloud .

  12. Activez les API Cloud Run, Artifact Registry, Cloud Build et Cloud Storage, si elles ne le sont pas déjà :

    Rôles requis pour activer les API

    Pour activer des API, vous devez disposer de l'autorisation serviceusage.services.enable. Si vous avez créé le projet, vous disposez probablement déjà de cette autorisation via le rôle Propriétaire (roles/owner). Sinon, vous pouvez obtenir cette autorisation via le rôle Administrateur Service Usage (roles/serviceusage.serviceUsageAdmin). Découvrez comment attribuer des rôles.

    gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com storage.googleapis.com
  13. Définissez votre région en tant que variable d'environnement :
    export REGION=REGION
  14. Créez un compte de service pour le job :
    gcloud iam service-accounts create gpu-job-sa
  15. Assurez-vous de disposer d'un [quota de GPU suffisant](/run/docs/configuring/jobs/gpu#request-quota) (par exemple, Nvidia L4) dans la région de votre choix.

Préparer votre application

Pour qu'un job de longue durée soit résilient, votre application doit :

  1. Enregistrez régulièrement la progression (point de contrôle) dans un emplacement persistant, tel qu'un bucket Cloud Storage installé.

    Vous disposez d'environ 10 secondes après SIGTERM avant que la tâche ne soit arrêtée. Ce délai n'est pas suffisant pour écrire un point de contrôle de modèle volumineux. Traitez le gestionnaire SIGTERM comme un vidage au mieux d'un petit état uniquement, et fiez-vous aux points de contrôle périodiques comme protection principale. Choisissez l'intervalle de point de contrôle en fonction de la quantité de travail que vous pouvez vous permettre de perdre, en gardant à l'esprit que des points de contrôle plus fréquents signifient plus de temps non consacré à un travail utile. L'objectif est de trouver le bon équilibre pour votre cas d'utilisation.

  2. Restaurez l'état à partir du dernier point de contrôle au démarrage.

  3. Écoutez le signal SIGTERM, que Cloud Run envoie 10 secondes avant le processus de maintenance, et essayez d'enregistrer un point de contrôle final avant de quitter.

L'exemple suivant montre comment implémenter ces fonctionnalités.

Créer le code de l'application

  1. Créez un répertoire pour votre projet :

    mkdir gc-run-gpu-resilience
    cd gc-run-gpu-resilience
    
  2. Créez un fichier nommé task.py et collez-y le script suivant pour simuler un processus d'entraînement ou de traitement par lot compatible avec les points de contrôle et la gestion des signaux :

    import os
    import sys
    import time
    import signal
    
    # The checkpoint directory will be mounted to this path
    CHECKPOINT_DIR = "/checkpoints"
    # Cloud Run sets these variables automatically
    JOB_NAME = os.environ.get("CLOUD_RUN_JOB", "local-job")
    TASK_INDEX = os.environ.get("CLOUD_RUN_TASK_INDEX", "0")
    CHECKPOINT_FILE = os.path.join(
        CHECKPOINT_DIR, f"{JOB_NAME}-task-{TASK_INDEX}-checkpoint.txt")
    TOTAL_STEPS = 100
    current_step = 0
    is_exiting = False
    
    def save_checkpoint(step):
        print(f"Saving checkpoint at step {step} to {CHECKPOINT_FILE}...")
        try:
            # Write to a temp file and rename to ensure atomicity
            temp_file = CHECKPOINT_FILE + ".tmp"
            with open(temp_file, "w") as f:
                f.write(str(step))
            os.rename(temp_file, CHECKPOINT_FILE)
            print("Checkpoint saved successfully.")
        except Exception as e:
            print(f"Failed to save checkpoint: {e}")
    
    def load_checkpoint():
        print(f"Looking for a checkpoint at {CHECKPOINT_FILE}...")
        global current_step
        if os.path.exists(CHECKPOINT_FILE):
            try:
                with open(CHECKPOINT_FILE, "r") as f:
                    current_step = int(f.read().strip())
                print(f"Resuming from checkpointed step {current_step}.")
            except Exception as e:
                print(f"Failed to load checkpoint: {e}. Starting from scratch.")
        else:
            print("No checkpoint found. Starting from scratch.")
    
    def sigterm_handler(signum, frame):
        global is_exiting
        print("Received SIGTERM signal from platform. Initiating graceful shutdown...")
        is_exiting = True
    
    # Register the signal handler for SIGTERM
    signal.signal(signal.SIGTERM, sigterm_handler)
    
    # Load existing checkpoint if any to resume work
    load_checkpoint()
    
    # Simulate work loop
    while current_step < TOTAL_STEPS and not is_exiting:
        print(f"Processing step {current_step}/{TOTAL_STEPS}...")
        # Simulate heavy GPU computation
        time.sleep(1)
        current_step += 1
        # Periodically save checkpoint (e.g., every 5 steps)
        if current_step % 5 == 0:
            save_checkpoint(current_step)
    
    if is_exiting:
        # Save final state before exiting due to preemption
        # Best-effort only: with ~10s before SIGKILL this will not complete for
        # large state. Periodic checkpointing (above) is what actually protects
        # you.
        save_checkpoint(current_step)
        # Note that save_checkpoint already writes-then-renames for atomicity,
        # which is good, but the reason it matters here is that a truncated
        # checkpoint from an interrupted final save must not clobber the last
        # good one.
        print("Graceful shutdown complete. Exiting with error code to trigger retry.")
        # Exit with a non-zero code to signal to Cloud Run that the task
        # was interrupted and needs to be retried from the checkpoint.
        sys.exit(143)
        # Note that this statement deliberately consumes of the configured retry
        # attempts.
    
    print("Task completed successfully.")
    sys.exit(0)
    
  3. Créez un Dockerfile pour conteneuriser l'application :

Consultez cet exemple pour une implémentation plus détaillée et réaliste.

Créer un bucket Cloud Storage pour les points de contrôle

Créez un bucket Cloud Storage pour stocker les fichiers de point de contrôle, puis accordez au compte de service de votre job l'autorisation d'y accéder.

  1. Créez le bucket :

    gcloud storage buckets create gs://gpu-checkpoints-PROJECT_ID \
        --location REGION
    
  2. Attribuez le rôle "Administrateur des objets Storage" au compte de service du job :

    gcloud storage buckets add-iam-policy-binding gs://gpu-checkpoints-PROJECT_ID \
        --member="serviceAccount:gpu-job-sa@PROJECT_ID.iam.gserviceaccount.com" \
        --role="roles/storage.objectAdmin"
    

Déployer le job

Déployez le job dans Cloud Run :

gcloud beta run jobs deploy gpu-resilient-job \
    --source=. \
    --region REGION \
    --gpu 1 \
    --gpu-type nvidia-l4 \
    --no-gpu-zonal-redundancy \
    --task-timeout 168h \
    --max-retries 3 \
    --service-account gpu-job-sa@PROJECT_ID.iam.gserviceaccount.com \
    --add-volume=name=checkpoint-volume,type=cloud-storage,bucket=gpu-checkpoints-PROJECT_ID \
    --add-volume-mount=volume=checkpoint-volume,mount-path=/checkpoints

Notez les options critiques suivantes :

  • --task-timeout 168h : permet à la tâche de s'exécuter pendant 12 heures maximum.
  • --max-retries 3 : pour les jobs GPU dont le délai avant expiration est supérieur à une heure, nous vous recommandons de définir la configuration par défaut sur trois tentatives. Pour éviter les échecs silencieux lors de la maintenance de la plate-forme, nous vous déconseillons de définir 0 nouvelle tentative. Enfin, nous vous recommandons de prévoir un budget pour au moins une nouvelle tentative en cas d'échec lié à la maintenance, ainsi que pour des nouvelles tentatives supplémentaires en cas d'échec de l'application sur une période d'exécution de 12 heures.
  • --add-volume et --add-volume-mount : installe le bucket Cloud Storage sur /checkpoints afin que l'application puisse conserver l'état entre les nouvelles tentatives de tâche.

Exécuter et surveiller le job

  1. Exécuter le job :

    gcloud run jobs execute gpu-resilient-job --region REGION
    

    Cette commande renvoie un message semblable à celui-ci :

    Execution [gpu-resilient-job-abcde] has been successfully started.
    
    View details about this execution by running `gcloud run jobs executions
    describe gpu-resilient-job-abcde`.
    
    See logs for this execution at: https://console.cloud.google.com/logs/viewer?project=YOUR_PROJECT_ID&resource=cloud_run_job/job_name/my-job/location/my-region/execution_id/gpu-resilient-job-abcde
    

    La chaîne gpu-resilient-job-abcde est l'identifiant unique généré automatiquement pour cette tentative d'exécution spécifique. Vous pouvez l'utiliser pour surveiller ou annuler cette exécution spécifique. Le résultat inclut également un lien direct vers la page de l'explorateur de journaux dans la console Google Cloud pour afficher les résultats ou les erreurs de vos tâches de job.

  2. Surveillez les journaux. Si un événement de maintenance se produit, Cloud Run consigne un avertissement. Voici ce qui s'affiche alors :

    • Les journaux de votre application : Received SIGTERM signal from platform. Initiating graceful shutdown... suivi de Saving checkpoint....
    • Un journal système indiquant la préemption : Container instance preemption initiated by the platform due to underlying hardware maintenance. avec le motif INSTANCE_PREEMPTED.
    • Cloud Run redémarre automatiquement la tâche.
    • Journaux de votre application au redémarrage : Resuming from checkpointed step X..

Effectuer un nettoyage

Pour éviter que des frais supplémentaires ne soient facturés sur votre compte Google Cloud , supprimez toutes les ressources que vous avez déployées avec ce guide de démarrage rapide.

Supprimer votre dépôt

Cloud Run ne facture que la durée d'exécution de votre job. Toutefois, des frais peuvent vous être facturés pour le stockage de l'image de conteneur dans Artifact Registry. Pour supprimer des dépôts Artifact Registry, suivez les étapes de la section Supprimer des dépôts de la documentation Artifact Registry.

Supprimer votre job

Les jobs Cloud Run n'engendrent des coûts que lorsqu'une tâche de job est en cours d'exécution. Pour supprimer votre job Cloud Run, suivez l'une de ces étapes :

Console

Pour supprimer une tâche, procédez comme suit :

  1. Dans la console Google Cloud , accédez à Cloud Run :

    Accédez à Cloud Run

  2. Recherchez le job que vous souhaitez supprimer dans la liste des jobs, puis cliquez sur la case correspondante pour le sélectionner.

  3. Cliquez sur Supprimer. Cela met fin à toutes les exécutions de jobs en cours et à toutes les instances de conteneur en cours d'exécution.

gcloud

Pour supprimer un job, exécutez la commande suivante :

gcloud run jobs delete JOB_NAME

Remplacez JOB_NAME par le nom de la tâche.

Supprimer votre projet de test

La suppression de votre projet Google Cloud arrête la facturation de toutes les ressources de ce projet. Pour libérer toutes les ressources Google Cloud de votre projet, procédez comme suit :

    Supprimer un projet Google Cloud  :

    gcloud projects delete PROJECT_ID

Supprimez le bucket Cloud Storage :

gcloud storage buckets delete gs://gpu-checkpoints-PROJECT_ID

Étapes suivantes