設計彈性的長時間執行 GPU 工作

本教學課程說明如何設計及部署具備韌性、啟用 GPU 的 Cloud Run 工作,執行時間最長可達 12 小時。

雖然 Cloud Run 支援延長工作逾時時間,但並未提供不中斷執行的服務水準協議。由於啟用 GPU 的工作不會即時遷移,因此每週例行維護作業會在執行期間終止這些工作。因此,任何長時間執行的工作都應預期至少會中斷一次。為確保工作順利完成,且不浪費運算資源,請將應用程式設計為可設定檢查點並繼續執行,而非依賴單一不中斷的執行作業。

本教學課程將引導您實作檢查點、處理平台 SIGTERM 信號,以及設定重試次數上限。

目標

在本教學課程中,您將執行下列操作:

  • 準備 Python 應用程式,實作檢查點功能並處理 SIGTERM 信號。
  • 建立 Cloud Storage bucket 來保留檢查點。
  • 建構及部署 Cloud Run 工作,並啟用 GPU、設定 12 小時的工作逾時時間,以及重試政策。
  • 執行工作,並瞭解如何監控記錄中的先占事件。

費用

在本文件中,您將使用下列 Google Cloud計費元件:

您可以使用 Pricing Calculator,根據預測用量估算費用。

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

事前準備

  1. 登入 Google Cloud 帳戶。如果您未曾使用過 Google Cloud,歡迎 建立帳戶,親自體驗實際使用我們產品的成效。新客戶還能獲得價值 $300 美元的免費抵免額,用於執行、測試及部署工作負載。
  2. 安裝 Google Cloud CLI。

  3. 若您採用的是外部識別資訊提供者 (IdP),請先 使用聯合身分登入 gcloud CLI。

  4. 執行下列指令,初始化 gcloud CLI:

    gcloud init
  5. 建立或選取 Google Cloud 專案。

    選取或建立專案所需的角色

    • 選取專案:選取專案時,不需具備特定 IAM 角色,只要您已獲派某個專案的角色,即可選取該專案。
    • 建立專案:如要建立專案,您必須具備專案建立者角色 (roles/resourcemanager.projectCreator),其中包含 resourcemanager.projects.create 權限。瞭解如何授予角色。
    • 建立 Google Cloud 專案:

      gcloud projects create PROJECT_ID

      將 PROJECT_ID 替換為您要建立的 Google Cloud 專案名稱。

    • 選取您建立的 Google Cloud 專案:

      gcloud config set project PROJECT_ID

      將 PROJECT_ID 替換為 Google Cloud 專案名稱。

  6. 確認 Google Cloud 專案已啟用計費功能。

  7. 安裝 Google Cloud CLI。

  8. 若您採用的是外部識別資訊提供者 (IdP),請先 使用聯合身分登入 gcloud CLI。

  9. 執行下列指令,初始化 gcloud CLI:

    gcloud init
  10. 建立或選取 Google Cloud 專案。

    選取或建立專案所需的角色

    • 選取專案:選取專案時,不需具備特定 IAM 角色,只要您已獲派某個專案的角色,即可選取該專案。
    • 建立專案:如要建立專案,您必須具備專案建立者角色 (roles/resourcemanager.projectCreator),其中包含 resourcemanager.projects.create 權限。瞭解如何授予角色。
    • 建立 Google Cloud 專案:

      gcloud projects create PROJECT_ID

      將 PROJECT_ID 替換為您要建立的 Google Cloud 專案名稱。

    • 選取您建立的 Google Cloud 專案:

      gcloud config set project PROJECT_ID

      將 PROJECT_ID 替換為 Google Cloud 專案名稱。

  11. 確認 Google Cloud 專案已啟用計費功能。

  12. 啟用 Cloud Run、Artifact Registry、Cloud Build 和 Cloud Storage API (如有尚未啟用的 API):

    啟用 API 所需的角色

    您必須具備 serviceusage.services.enable 權限,才能啟用 API。如果您建立了專案,可能已透過「擁有者」角色 (roles/owner) 取得這項權限。否則,您可以透過「Service Usage 管理員」角色 (roles/serviceusage.serviceUsageAdmin) 取得這項權限。瞭解如何授予角色。

    gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com storage.googleapis.com
  13. 將區域設為環境變數:
    export REGION=REGION
  14. 為工作建立服務帳戶:
    gcloud iam service-accounts create gpu-job-sa
  15. 確認所選區域有 [足夠的 GPU 配額](/run/docs/configuring/jobs/gpu#request-quota) (例如 NVIDIA L4)。

準備應用程式

如要讓長時間執行的工作具有彈性,應用程式必須:

  1. 定期將進度儲存 (檢查點) 至永久位置,例如掛接的 Cloud Storage bucket。

    SIGTERM 之後,您有大約 10 秒的時間,工作就會終止。這段時間不足以寫入大型模型檢查點。請將 SIGTERM 處理常式視為僅盡力清除小型狀態,並以定期檢查點做為主要保護措施。請根據您能承受的工作損失量,選擇檢查點間隔,但請注意,檢查點越頻繁,代表未進行有用工作的時間越多。目標是根據您的用途,達到適當的平衡。

  2. 啟動時從上一個檢查點還原狀態。

  3. Cloud Run 會在維護程序開始前 10 秒傳送 SIGTERM 信號,請監聽這個信號,並在結束前嘗試儲存最終檢查點。

以下範例說明如何實作這些功能。

建立應用程式程式碼

  1. 為專案建立目錄:

    mkdir gc-run-gpu-resilience
    cd gc-run-gpu-resilience
    
  2. 建立名為 task.py 的檔案,並貼上以下指令碼,模擬支援檢查點和信號處理的訓練或批次處理程序:

    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. 建立 Dockerfile,將應用程式容器化:

如需更詳細、實際的實作範例,請參閱這個範例。

建立 Cloud Storage bucket 來存放檢查點

建立 Cloud Storage bucket 來儲存檢查點檔案,並授予工作服務帳戶存取權限。

  1. 建立 bucket:

    gcloud storage buckets create gs://gpu-checkpoints-PROJECT_ID \
        --location REGION
    
  2. 將 Storage 物件管理員角色授予工作的服務帳戶:

    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"
    

部署工作

將工作部署至 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

請注意下列重要標記:

  • --task-timeout 168h:允許工作執行最多 12 小時。
  • --max-retries 3:如果 GPU 工作的逾時時間超過 1 小時,建議將預設設定設為重試 3 次。為避免平台維護期間發生無訊息失敗,建議不要將重試次數設為 0。最後,建議您預留至少 1 次重試的預算,以因應維護相關失敗,並預留額外重試的預算,以因應 12 小時執行期間內的應用程式失敗。
  • --add-volume 和 --add-volume-mount:將 Cloud Storage bucket 掛接至 /checkpoints,讓應用程式在重試工作時能保留狀態。

執行及監控工作

  1. 執行工作:

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

    這個指令會傳回類似以下的訊息:

    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
    

    字串 gpu-resilient-job-abcde 是系統為這項特定執行嘗試自動生成的專屬 ID。您可以使用這個 ID 監控或取消這項特定執行作業。輸出內容也包含 Google Cloud 控制台中 Logs Explorer 頁面的直接連結,方便您查看工作任務的輸出內容或錯誤。

  2. 監控記錄。如果發生維護事件,Cloud Run 會記錄警告。您會看到:

    • 應用程式記錄:Received SIGTERM signal from platform. Initiating graceful shutdown...,後面接著 Saving checkpoint...。
    • 系統記錄指出預先分配:Container instance preemption initiated by the platform due to underlying hardware maintenance.,原因:INSTANCE_PREEMPTED。
    • Cloud Run 會自動重新啟動工作。
    • 重新啟動時的應用程式記錄:Resuming from checkpointed step X.。

清除所用資源

為避免系統向您的 Google Cloud 帳戶收取額外費用,請刪除您透過本快速入門導覽課程部署的所有資源。

刪除存放區

Cloud Run 只會依據工作執行時間收費。不過,您可能仍須支付在 Artifact Registry 儲存容器映像檔的費用。如要刪除 Artifact Registry 存放區,請按照 Artifact Registry 說明文件中的「刪除存放區」步驟操作。

刪除工作

Cloud Run 工作只有在執行工作任務時才會產生費用。如要刪除 Cloud Run 工作,請按照下列任一步驟操作:

控制台

如要刪除工作:

  1. 前往 Google Cloud 控制台的「Cloud Run」頁面:

    前往 Cloud Run。

  2. 在工作清單中找到要刪除的工作,然後勾選核取方塊。

  3. 按一下「Delete」(刪除)。這會終止所有執行中的工作和容器執行個體。

gcloud

如要刪除工作,請執行下列指令:

gcloud run jobs delete JOB_NAME

將 JOB_NAME 替換為工作名稱。

刪除測試專案

刪除 Google Cloud 專案後,系統會停止向該專案中的所有資源計費。如要釋出專案中的所有 Google Cloud 資源,請按照下列步驟操作:

    刪除 Google Cloud 專案:

    gcloud projects delete PROJECT_ID

刪除 Cloud Storage bucket:

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

後續步驟