תכנון משימות GPU עמידות לטווח ארוך

במדריך הזה מוסבר איך לתכנן ולפרוס משימות גמישות ב-Cloud Run עם GPU, שפועלות עד 12 שעות.

למרות ש-Cloud Run תומך בזמנים קצובים לתפוגה של משימות מורחבות, הוא לא מציע הסכם רמת שירות (SLA) להפעלה ללא הפרעות. משימות שמופעלות באמצעות GPU לא עוברות מיגרציה פעילה, ולכן תחזוקה שבועית שגרתית תסיים אותן באמצע ההרצה. לכן, כל משימה ארוכה צפויה להיקטע לפחות פעם אחת. כדי לוודא שהמשימה תסתיים בהצלחה בלי לבזבז משאבי מחשוב, כדאי לתכנן את האפליקציה כך שתבצע נקודות ביקורת ותמשיך, במקום להסתמך על הפעלה אחת ללא הפרעה.

במדריך הזה מוסבר איך להטמיע נקודות ביקורת, לטפל באותות של פלטפורמת SIGTERM ולהגדיר את מספר הניסיונות החוזרים המקסימלי.

מטרות

במדריך הזה תלמדו:

  • מכינים אפליקציית Python שמטמיעה נקודות בדיקה ומטפלת באותות SIGTERM.
  • יוצרים קטגוריה של Cloud Storage כדי לשמור נקודות ביקורת.
  • מפתחים ומפעילים משימה ב-Cloud Run עם GPU, זמן קצוב לתפוגה של 12 שעות ומדיניות מוגדרת לניסיונות חוזרים.
  • מריצים את המשימה ומבינים איך לעקוב אחרי אירועי הפקעה ביומנים.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

בעזרת מחשבון העלויות אתם יכולים להעריך את העלויות בהתאם לשימוש הצפוי.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

לפני שמתחילים

  1. נכנסים לחשבון Google Cloud . אנחנו ממליצים למשתמשים חדשים ב- Google Cloud ליצור חשבון כדי שיוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  2. התקינו את ה-CLI של Google Cloud.

  3. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  4. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    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. התקינו את ה-CLI של Google Cloud.

  8. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  9. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    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. מפעילים את ממשקי ה-API של Cloud Run,‏ Artifact Registry,‏ Cloud Build ו-Cloud Storage, אם הם עדיין לא מופעלים:

    התפקידים שנדרשים כדי להפעיל ממשקי API

    כדי להפעיל ממשקי API, צריך את ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד "בעלים" (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד "אדמין בממשק 'שימוש בשירות'" (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.

    יש לכם בערך 10 שניות אחרי SIGTERM לפני שהמשימה תסתיים. זה לא מספיק זמן כדי לכתוב נקודת ביקורת של מודל גדול. התייחסו ל-handler של SIGTERM כאל ניקוי של מצב קטן בלבד, והסתמכו על נקודות ביקורת תקופתיות כהגנה העיקרית שלכם. בחרו את מרווח הזמן בין נקודות הבדיקה בהתאם לכמות העבודה שאתם יכולים להרשות לעצמכם לאבד, וזכרו שככל שיש יותר נקודות בדיקה, כך אתם משקיעים פחות זמן בעבודה מועילה. המטרה היא להגיע לאיזון שמתאים לתרחיש לדוגמה שלכם.

  2. שחזור המצב מנקודת השמירה האחרונה בזמן ההפעלה.

  3. להאזין לאות SIGTERM, ש-Cloud Run שולח 10 שניות לפני תהליך התחזוקה, ולנסות לשמור נקודת ביקורת אחרונה לפני היציאה.

הדוגמה הבאה ממחישה איך להטמיע את התכונות האלה.

יצירת קוד האפליקציה

  1. יוצרים ספרייה לפרויקט:

    mkdir gc-run-gpu-resilience
    cd gc-run-gpu-resilience
    
  2. יוצרים קובץ בשם task.py ומדביקים בו את הסקריפט הבא כדי לדמות תהליך אימון או עיבוד ברצף (batch processing) שתומך בנקודות בקרה ובטיפול באותות:

    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 לנקודות בקרה

יוצרים קטגוריה של Cloud Storage כדי לאחסן את קובצי נקודות הבדיקה, ונותנים לחשבון השירות של המשימה הרשאת גישה לקטגוריה.

  1. יוצרים את הקטגוריה:

    gcloud storage buckets create gs://gpu-checkpoints-PROJECT_ID \
        --location REGION
    
  2. מקצים את התפקיד "אדמין של אובייקטים באחסון" לחשבון השירות של המשימה:

    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 עם זמן קצוב לתפוגה של יותר משעה, מומלץ להגדיר את ברירת המחדל של 3 ניסיונות חוזרים. כדי למנוע כשלים שקטים במהלך תחזוקת הפלטפורמה, מומלץ לא להגדיר 0 ניסיונות חוזרים. לסיום, מומלץ להקצות בתקציב לפחות ניסיון חוזר אחד לכשל שקשור לתחזוקה, וגם ניסיונות חוזרים נוספים לכשלים באפליקציה במהלך תקופת הרצה של 12 שעות.
  • ‫--add-volume ו---add-volume-mount: טעינת הקטגוריה של Cloud Storage אל /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 היא המזהה הייחודי שנוצר אוטומטית לניסיון ההרצה הספציפי הזה. אתם יכולים להשתמש בו כדי לעקוב אחרי ההפעלה הספציפית הזו או לבטל אותה. הפלט כולל גם קישור ישיר לדף של Logs Explorer במסוף Google Cloud , שבו אפשר לראות את הפלט או השגיאות מהמשימות של ה-job.

  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. נכנסים ל-Cloud Run במסוף Google Cloud :

    כניסה ל-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:

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

המאמרים הבאים