Diseña trabajos de GPU resilientes y de larga duración

En este instructivo, se explica cómo diseñar e implementar trabajos de Cloud Run resilientes y habilitados para GPU que se ejecutan durante un máximo de 12 horas.

Aunque Cloud Run admite tiempos de espera de tareas extendidos, no ofrece un ANS para la ejecución ininterrumpida. Debido a que las tareas habilitadas para GPU no se migran en vivo, el mantenimiento semanal de rutina las finalizará a mitad de la ejecución. Por lo tanto, se debe esperar que cualquier tarea extendida se interrumpa al menos una vez. Para asegurarte de que tu trabajo finalice correctamente sin desperdiciar recursos de procesamiento, diseña tu aplicación para que cree puntos de control y se reanude, en lugar de depender de una sola ejecución sin interrupciones.

En este instructivo, se explica cómo implementar puntos de control, controlar las señales de la plataforma SIGTERM y configurar la cantidad máxima de reintentos.

Objetivos

En este instructivo, harás lo siguiente:

  • Prepara una aplicación de Python que implemente puntos de control y controle los indicadores SIGTERM.
  • Crea un bucket de Cloud Storage para conservar los puntos de control.
  • Compilar e implementar un trabajo de Cloud Run con la GPU habilitada, un tiempo de espera de la tarea de 12 horas y una política de reintentos configurada
  • Ejecutar el trabajo y comprender cómo supervisar los eventos de interrupción en los registros

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios.

Es posible que los usuarios nuevos de Google Cloud cumplan con los requisitos para acceder a una prueba gratuita.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud . Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. Instala Google Cloud CLI.

  3. Si usas un proveedor de identidad externo (IdP), primero debes acceder a la gcloud CLI con tu identidad federada.

  4. Para inicializar gcloud CLI, ejecuta el siguiente comando:

    gcloud init
  5. Crea o selecciona un proyecto de Google Cloud .

    Roles necesarios para seleccionar o crear un proyecto

    • Seleccionar un proyecto: Para seleccionar un proyecto, no se requiere un rol de IAM específico. Puedes seleccionar cualquier proyecto en el que se te haya otorgado un rol.
    • Crea un proyecto: Para crear un proyecto, necesitas el rol de creador de proyectos (roles/resourcemanager.projectCreator), que contiene el permiso resourcemanager.projects.create. Obtén más información para otorgar roles.
    • Crea un proyecto de Google Cloud :

      gcloud projects create PROJECT_ID

      Reemplaza PROJECT_ID por un nombre para el proyecto de Google Cloud que estás creando.

    • Selecciona el proyecto de Google Cloud que creaste:

      gcloud config set project PROJECT_ID

      Reemplaza PROJECT_ID por el nombre de tu proyecto de Google Cloud .

  6. Verifica que la facturación esté habilitada para tu proyecto de Google Cloud .

  7. Instala Google Cloud CLI.

  8. Si usas un proveedor de identidad externo (IdP), primero debes acceder a la gcloud CLI con tu identidad federada.

  9. Para inicializar gcloud CLI, ejecuta el siguiente comando:

    gcloud init
  10. Crea o selecciona un proyecto de Google Cloud .

    Roles necesarios para seleccionar o crear un proyecto

    • Seleccionar un proyecto: Para seleccionar un proyecto, no se requiere un rol de IAM específico. Puedes seleccionar cualquier proyecto en el que se te haya otorgado un rol.
    • Crea un proyecto: Para crear un proyecto, necesitas el rol de creador de proyectos (roles/resourcemanager.projectCreator), que contiene el permiso resourcemanager.projects.create. Obtén más información para otorgar roles.
    • Crea un proyecto de Google Cloud :

      gcloud projects create PROJECT_ID

      Reemplaza PROJECT_ID por un nombre para el proyecto de Google Cloud que estás creando.

    • Selecciona el proyecto de Google Cloud que creaste:

      gcloud config set project PROJECT_ID

      Reemplaza PROJECT_ID por el nombre de tu proyecto de Google Cloud .

  11. Verifica que la facturación esté habilitada para tu proyecto de Google Cloud .

  12. Habilita las APIs de Cloud Run, Artifact Registry, Cloud Build y Cloud Storage, si aún no están habilitadas:

    Roles necesarios para habilitar las APIs

    Para habilitar las APIs, necesitas el permiso serviceusage.services.enable. Si creaste el proyecto, es probable que ya tengas este permiso a través del rol de propietario (roles/owner). De lo contrario, puedes obtenerlo a través del rol de administrador de Service Usage (roles/serviceusage.serviceUsageAdmin). Obtén más información para otorgar roles.

    gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com storage.googleapis.com
  13. Configura tu región como una variable de entorno:
    export REGION=REGION
  14. Crea una cuenta de servicio para el trabajo:
    gcloud iam service-accounts create gpu-job-sa
  15. Asegúrate de tener [suficiente cuota de GPU](/run/docs/configuring/jobs/gpu#request-quota) (p.ej., Nvidia L4) en la región que elegiste.

Prepara tu aplicación

Para que un trabajo de larga duración sea resiliente, tu aplicación debe cumplir con los siguientes requisitos:

  1. Guarda el progreso de forma periódica (puntos de control) en una ubicación persistente, como un bucket de Cloud Storage activado.

    Tienes aproximadamente 10 segundos después de SIGTERM antes de que se finalice la tarea. Este tiempo no es suficiente para escribir un punto de control de modelo grande. Trata el controlador SIGTERM como un vaciado de mejor esfuerzo solo para estados pequeños y confía en los puntos de control periódicos como tu protección principal. Elige el intervalo de puntos de control en función de la cantidad de trabajo que puedas permitirte perder, teniendo en cuenta que los puntos de control más frecuentes implican más tiempo sin realizar trabajo útil. El objetivo es lograr el equilibrio adecuado para tu caso de uso.

  2. Restablece el estado desde el último punto de control durante el inicio.

  3. Escucha la señal SIGTERM, que Cloud Run envía 10 segundos antes del proceso de mantenimiento, y trata de guardar un punto de control final antes de salir.

En el siguiente ejemplo, se muestra cómo implementar estas funciones.

Crea el código de la aplicación

  1. Crea un directorio para tu proyecto:

    mkdir gc-run-gpu-resilience
    cd gc-run-gpu-resilience
    
  2. Crea un archivo llamado task.py y pega la siguiente secuencia de comandos para simular un proceso de entrenamiento o procesamiento por lotes que admita la creación de puntos de control y el manejo de indicadores:

    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. Crea un Dockerfile para organizar la aplicación en contenedores:

Consulta este ejemplo para ver una implementación más detallada y realista.

Crea un bucket de Cloud Storage para los puntos de control

Crea un bucket de Cloud Storage para almacenar los archivos de punto de control y otorga permiso a la cuenta de servicio de tu trabajo para acceder a él.

  1. Crea el bucket:

    gcloud storage buckets create gs://gpu-checkpoints-PROJECT_ID \
        --location REGION
    
  2. Otorga el rol de administrador de objetos de Storage a la cuenta de servicio del trabajo:

    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"
    

Implementa el trabajo

Implementa el trabajo en 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

Ten en cuenta las siguientes marcas críticas:

  • --task-timeout 168h: Permite que la tarea se ejecute durante un máximo de 12 horas.
  • --max-retries 3: Para los trabajos de GPU con un tiempo de espera superior a 1 hora, recomendamos establecer la configuración predeterminada de 3 reintentos. Para evitar fallas silenciosas durante el mantenimiento de la plataforma, te recomendamos que no configures 0 reintentos. Por último, te recomendamos que presupuestes al menos 1 reintento para las fallas relacionadas con el mantenimiento, así como reintentos adicionales para las fallas de la aplicación durante un período de ejecución de 12 horas.
  • --add-volume y --add-volume-mount: Activa el bucket de Cloud Storage en /checkpoints para que la aplicación pueda conservar el estado en los reintentos de tareas.

Ejecuta y supervisa el trabajo

  1. Ejecuta el trabajo:

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

    Este comando devuelve un mensaje similar al siguiente:

    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 cadena gpu-resilient-job-abcde es el ID único generado de forma automática para este intento de ejecución específico. Puedes usarlo para supervisar o cancelar esta ejecución específica. El resultado también incluye un vínculo directo a la página del Explorador de registros en la consola de Google Cloud para ver los resultados o los errores de las tareas del trabajo.

  2. Supervisa los registros. Si se produce un evento de mantenimiento, Cloud Run registra una advertencia. Verás lo siguiente:

    • Los registros de tu aplicación: Received SIGTERM signal from platform. Initiating graceful shutdown... seguido de Saving checkpoint....
    • Un registro del sistema que indica la interrupción: Container instance preemption initiated by the platform due to underlying hardware maintenance. con el motivo INSTANCE_PREEMPTED.
    • Cloud Run reinicia la tarea automáticamente.
    • Los registros de tu aplicación cuando se reinicia: Resuming from checkpointed step X..

Realiza una limpieza

Para evitar que se apliquen cargos adicionales a tu cuenta de Google Cloud , borra todos los recursos que implementaste con esta guía de inicio rápido.

Borra tu repositorio

Cloud Run solo cobra por el tiempo que se ejecuta tu trabajo. Sin embargo, es posible que se te cobre por el almacenamiento de la imagen de contenedor en Artifact Registry. Para borrar repositorios de Artifact Registry, sigue los pasos que se indican en Borra repositorios en la documentación de Artifact Registry.

Borra tu trabajo

Los trabajos de Cloud Run solo generan costos cuando se ejecuta una tarea de trabajo. Para borrar tu trabajo de Cloud Run, sigue uno de estos pasos:

Console

Para borrar un trabajo, sigue estos pasos:

  1. En la consola Google Cloud , ve a Cloud Run:

    Ir a Cloud Run

  2. Busca el trabajo que deseas borrar en la lista de trabajos y haz clic en su casilla de verificación para seleccionarlo.

  3. Haz clic en Borrar. Esto finaliza todas las ejecuciones de trabajos en curso y todas las instancias de contenedor en ejecución.

gcloud

Para borrar un trabajo, ejecuta el siguiente comando:

gcloud run jobs delete JOB_NAME

Reemplaza JOB_NAME por el nombre del trabajo.

Borra tu proyecto de prueba

Si borras tu proyecto de Google Cloud , se detendrá la facturación de todos los recursos de ese proyecto. Para liberar todos los recursos de Google Cloud en tu proyecto, sigue estos pasos:

    Borra un proyecto de Google Cloud :

    gcloud projects delete PROJECT_ID

Borra el bucket de Cloud Storage:

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

¿Qué sigue?