שימוש בתמונת מערכת הפעלה מותאמת אישית

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

אם אתם משתמשים בתמונת מערכת הפעלה בהתאמה אישית עם TPU, חשוב לזכור את כתבי הוויתור הבאים:

  • ‫Google מספקת תמונות של Ubuntu עם תמיכה לטווח ארוך (LTS) שעברו אופטימיזציה ל-TPU כברירת מחדל. השינויים במערכת ההפעלה שמפורטים בדף הזה מאומתים רק לתמונות של Ubuntu LTS שעברו אופטימיזציה ל-TPU ונתמכות על ידי Google.
  • אתם אחראים להסיק את השינויים הנדרשים במערכת ההפעלה עבור כל הפצה אחרת של מערכת ההפעלה או תמונות בהתאמה אישית. ‫Google לא מבטיחה שהשינויים ב-Ubuntu שמפורטים בדף הזה יפעלו עם הפצות אחרות של מערכת הפעלה או עם תמונה אחרת של Ubuntu עם ליבה מותאמת אישית.
  • ‫Google לא יוצרת או מספקת בדיקות לתמונות של מערכות הפעלה אחרות מלבד תמונות ברירת המחדל של Ubuntu LTS שעברו אופטימיזציה ל-TPU. צריך ליצור ולבדוק את קובץ האימג' של מערכת ההפעלה בהתאמה אישית.

מידע נוסף על תמונות ברירת המחדל של Ubuntu LTS שעברו אופטימיזציה ל-TPU זמין במאמר בנושא תמונות של מערכת הפעלה ל-TPU.

דרישות מוקדמות

בתמונת הבסיס צריכים להיות מותקנים הרכיבים הבאים:

  • Python 3
  • ‫CLI של gcloud

ביצוע שינויים במהלך יצירת תמונה

מבצעים את השינויים הבאים כשיוצרים תמונת Ubuntu בהתאמה אישית.

קישור מכשירי TPU ל-VFIO

כדי לאפשר למערכת ההפעלה של האורח לגשת לחומרת TPU, צריך לקשר את מכשירי ה-TPU למנהל ההתקן vfio-pci.

  1. יוצרים קובץ כללי udev בשם 99-tpu-vfiopci.rules בתיקייה /etc/udev/rules.d/:

    # Rules for binding vfio-enabled TPU devices to vfio-pci.
    
    # v5p
    SUBSYSTEM=="pci", ACTION=="add", ATTRS{vendor}=="0x1ae0", ATTRS{device}=="0x0062", ATTRS{subsystem_vendor}=="0x1ae0", ATTRS{subsystem_device}=="0x00ad", DRIVER!="vfio-pci", TAG+="bind_to_vfio_pci"
    
    # v6e
    SUBSYSTEM=="pci", ACTION=="add", ATTRS{vendor}=="0x1ae0", ATTRS{device}=="0x006f", ATTRS{subsystem_vendor}=="0x1ae0", ATTRS{subsystem_device}=="0x00d1", DRIVER!="vfio-pci", TAG+="bind_to_vfio_pci"
    
    # TPU7x
    SUBSYSTEM=="pci", ACTION=="add", ATTRS{vendor}=="0x1ae0", ATTRS{device}=="0x0076", ATTRS{subsystem_vendor}=="0x1ae0", ATTRS{subsystem_device}=="0x00f2", DRIVER!="vfio-pci", TAG+="bind_to_vfio_pci"
    
    # Bind all 'bind_to_vfio_pci' tagged devices to vfio-pci.
    TAG=="bind_to_vfio_pci", RUN+="/lib/udev/bind_to_vfio_pci.sh $kernel"
    
  2. יוצרים סקריפט בשם bind_to_vfio_pci.sh ב-/lib/udev/:

    #!/bin/bash
    #!/usr/bin/env bash
    
    # Run ./bind_to_vfio_pci.sh <DBDF>
    # Binds the device at <DBDF> to vfio-pci.
    # If the device is already bound to a driver, unbinds it first.
    
    # Load the vfio-pci module into the kernel. No-op if already loaded.
    modprobe vfio-pci
    
    DBDF_REGEX="^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}.[[:xdigit:]]$"
    
    unset BDF
    if [[ $1 =~ $DBDF_REGEX ]]; then
        BDF=$1
    else
        echo "Error: BDF arg ($1) is not in form dddd:bb:dd.f"
        exit 1
    fi
    
    PCI_PATH="/sys/bus/pci/devices/$BDF"
    
    echo "vfio-pci" > "$PCI_PATH/driver_override"
    
    PCI_DRIVER_PATH="$PCI_PATH/driver"
    if [[ -d "$PCI_DRIVER_PATH" ]]; then
        curr_driver=$(readlink "$PCI_DRIVER_PATH")
            curr_driver=${curr_driver##*/}
        if [[ $curr_driver == "vfio-pci" ]]; then
            echo "$BDF already bound to vfio-pci"
            exit 0
        else
            echo "$BDF" > "$PCI_DRIVER_PATH/unbind"
            if [[ -d "$PCI_DRIVER_PATH" ]]; then
                echo "Error: Unable to unbind $PCI_DRIVER_PATH"
                exit 1
            fi
            echo "Unbound $BDF from driver $curr_driver"
        fi
    fi
    echo "$BDF" > /sys/bus/pci/drivers_probe
    echo "Bound $BDF to vfio-pci"
    
    # Grant read/write access on VFIO device to all users
    IOMMU_GROUP=$(readlink "$PCI_PATH/iommu_group" | xargs basename)
    VFIO_DEV="/dev/vfio/$IOMMU_GROUP"
    if [[ -c "$VFIO_DEV" ]]; then
        chmod 0666 "$VFIO_DEV"
    else
        echo "$VFIO_DEV not found"
        exit 1
    fi
    
    # Set allow_unsafe_interrupts for x86 platforms.
    (uname -a | grep -q x86_64) && echo 1 > /sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts
    
    # This is only needed to avoid non-zero exit code from previous command.
    echo "All Done!"
    
  3. הופכים את הסקריפט לניתן להרצה:

    chmod +x /lib/udev/bind_to_vfio_pci.sh
    
  4. נותנים לכל המשתמשים במערכת גישה למכשיר ה-TPU:

    echo 'KERNEL=="accel*" MODE="0666"' >> /etc/udev/rules.d/99-tpu.rules
    

שינוי התמונה כדי לשפר את הביצועים

כדי להבטיח ביצועים אופטימליים, צריך לשנות את מגבלות המערכת והפרמטרים הבאים.

מגבלות זיכרון

כדי לאפשר לתהליך יחיד לנעול זיכרון ללא הגבלה, צריך לעדכן את /etc/security/limits.conf:

echo '*  hard  memlock  unlimited' >> /etc/security/limits.conf
echo '*  soft  memlock  unlimited' >> /etc/security/limits.conf

מגבלות על קבצים

כדי להגדיל את מספר הקבצים הפתוחים, מעדכנים את /etc/security/limits.conf:

echo "*    soft    nofile       100000" >> /etc/security/limits.conf
echo "*    hard    nofile       100000" >> /etc/security/limits.conf
echo "root soft    nofile       100000" >> /etc/security/limits.conf
echo "root hard    nofile       100000" >> /etc/security/limits.conf

פרמטרים של ליבה

מעדכנים את ההגדרות של GRUB (בדרך כלל ב-/etc/default/grub) כך שיכללו את הפרמטרים הבאים ב-GRUB_CMDLINE_LINUX:

  • ‫idle=poll: מונעת מהמעבד (CPU) להיכנס למצבי המתנה עם צריכת חשמל נמוכה.
  • ‫intel_iommu=on,sm_on: הפעלת יחידת ניהול זיכרון קלט-פלט של אינטל (IOMMU). נדרש לארכיטקטורות TPU7x ו-v5p.
  • ‫transparent_hugepage=always: הפעלת דפים גדולים שקופים (THP).

כדי לעדכן את פרמטרים של ליבת המערכת:

  1. כדי למנוע מה-CPU לעבור למצב המתנה עם צריכת חשמל נמוכה, מגדירים את המשתנה הבא, שבו תשתמשו בשלב הבא.

    kernel_cmdline="idle=poll"
    
  2. מפעילים את יחידת ניהול הזיכרון של קלט/פלט (IOMMU) של Intel. השלב הזה נדרש עבור TPU7x ו-TPU v5p.

    kernel_cmdline="${kernel_cmdline} intel_iommu=on,sm_on";
    sed -i "s/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"${kernel_cmdline}\"/" /etc/default/grub
    echo "Status: New kernel cmdline: $(cat /etc/default/grub | grep -e '^GRUB_CMDLINE_LINUX=')"
    
    update-grub
    
  3. הפעלת דפים שקופים גדולים (THP):

    echo "Status: Enabling THP"
    sed -i -r 's/GRUB_CMDLINE_LINUX="[a-zA-Z0-9_= ]*/& transparent_hugepage=always/' /etc/default/grub
    
    update-grub
    

התקנת סוכן vBar

סוכן vBar נדרש כדי שהרשת של חיבורים בין-שבביים (ICI) תפעל.

כדי להתקין את סוכן vBar, מריצים את הפקודות הבאות:

  1. אימות Docker באמצעות Artifact Registry:

    gcloud auth configure-docker us-docker.pkg.dev
    
  2. שליפת קובץ אימג' של Docker מ-Artifact Registry:

    docker pull gcr.io/cloud-tpu-v2-images/vbar_control_agent:0.0.1
    
  3. מריצים קונטיינר באמצעות קובץ האימג' של סוכן vBar:

    docker run --privileged --net=host vbar_control_agent:0.0.1
    

אופציונלי: התקנה והפעלה של הכלי AI Telemetry Collector

הכלי לאיסוף טלמטריה של AI פועל בתוך מכונת ה-TPU VM ומאפשר לכם לגשת למדדים של זמן ריצה ותשתית באמצעות Cloud Monitoring או באמצעות צינור ניטור משלכם שמבוסס על Prometheus. אתם יכולים להשתמש בכלי לאיסוף נתוני טלמטריה של AI עם מערכת הפעלה מותאמת אישית באמצעות ai-telemetry-collector קובץ האימג' של Docker. אפשר להתקין את התמונה במערכת ההפעלה המותאמת אישית ולהשתמש בקובץ config.yaml כדי להגדיר את מרווחי האיסוף, להפעיל או להשבית מדדים ספציפיים או לשנות את יעדי הייצוא.

כדי להתקין את הכלי לאיסוף נתוני טלמטריה של AI, מריצים את הפקודות הבאות:

  1. אימות Docker באמצעות Artifact Registry:

    gcloud auth configure-docker us-docker.pkg.dev
    
  2. שליפת קובץ אימג' של Docker מ-Artifact Registry:

    docker pull gcr.io/cloud-tpu-v2-images/ai-telemetry-collector:latest
    
  3. מריצים קונטיינר באמצעות קובץ האימג' של הכלי לאיסוף טלמטריה מ-AI עם הגדרת ברירת המחדל:

    docker run --privileged --net=host ai-telemetry-collector:latest
    

    למידע על שימוש בקובץ הגדרה בהתאמה אישית או על הוספה של קובצי הגדרה נוספים, אפשר לעיין במאמר AI Telemetry Collector.

ביצוע שינויים בזמן האתחול

מגדירים את התמונה כך שתבצע את המשימות שבקטעים הבאים בכל פעם שמפעילים מכונה וירטואלית. אתם יכולים להשתמש בכלי cloud-init כדי להגדיר משימות בזמן האתחול על ידי העברת מטא-נתונים למופעים. ההגדרות בקטעים הבאים משתמשות במודולים כמו write_files ו-runcmd. קטעי קוד שמגדירים קבצים לכתיבה צריכים להיכלל במפתח write_files:, ופקודות שצריך להריץ בזמן האתחול צריכות להיכלל במפתח runcmd: בהגדרות cloud-init.

הפעלת הסוכן ב-vBar

מפעילים את סוכן הבקרה vBar עם מזהי המשתמש והקבוצה המתאימים:

vbar_control_agent --logtostderr --gid= --uid=  --chroot= --census_enabled=false --loas_pwd_fallback_in_corp

הגדרת משתני סביבה

כדי לוודא שהסביבה מאותחלת בצורה נכונה לעומסי עבודה של TPU, צריך לאחזר משתני הגדרות זמן ריצה משרת המטא-נתונים של Compute Engine במהלך תהליך אתחול המערכת. כדי לעשות זאת, מוסיפים את קטע הקוד הבא לקטע write_files: בהגדרות של cloud-init. קטע הקוד הזה יוצר סקריפט בשם /var/scripts/configure-env-vars.sh. הסקריפט הזה מבצע אוטומציה של אחזור מאפיינים ממפתח המטא-נתונים tpu-env ושומר אותם ב-/${HOME}/tpu-env כדי להשתמש בהם במערך התוכנה של TPU.

 - path: /var/scripts/configure-env-vars.sh
    permissions: 0444
    owner: root
    content: |
      grep -q CLOUDSDK_PYTHON /etc/environment || echo "CLOUDSDK_PYTHON=/usr/bin/python3" >> /etc/environment

      export HOME=/home/tpu-runtime
      curl -s 'http://metadata.google.internal/computeMetadata/v1/instance/attributes/tpu-env' -H 'Metadata-Flavor: Google' > /tmp/tpu-env.yaml

      eval $(python3 -c '''
      import yaml
      stream_in=open("/tmp/tpu-env.yaml", "r")
      for k,v in yaml.safe_load(stream_in).items():
        print("{var}=\"{value}\"".format(var = k, value = str(v)))
      ''' > "/${HOME}/tpu-env"
      )

      rm -f "/tmp/tpu-env.yaml"

      printenv
      cat ${HOME}/tpu-env

אחזור מטא-נתונים של מכונה וירטואלית

קטע הקוד הבא יוצר סקריפט בשם /var/scripts/get-vm-metadata.py, כלי Python לשליחת שאילתות לשרת המטא-נתונים באופן פרוגרמטי לגבי מאפיינים ספציפיים של מופע ותגי מטא-נתונים בהתאמה אישית. מוסיפים את השורה הבאה לקטע write_files: בהגדרות של cloud-init:

 - path: /var/scripts/get-vm-metadata.py
    permissions: 0444
    owner: root
    content: |
      import sys, requests, os

      if len(sys.argv) < 2:
        sys.stderr.write('Must provide key')
        os._exit(1)

      key = sys.argv[1]
      default = None
      if len(sys.argv) > 2:
        default = sys.argv[2]

      attribute_type = 'attributes'
      if len(sys.argv) > 3:
        attribute_type = sys.argv[3]

      request = requests.get("http://metadata.google.internal/computeMetadata/v1/instance/{}/{}".format(attribute_type, key), headers={'Metadata-Flavor': 'Google'})
      if request.status_code == 200:
        print(request.content)
      elif request.status_code == 404 or request.status_code == '403':
        sys.stderr.write('Metadata key: {} does not exist\n'.format(key))
        if default:
          print(default)
      else:
        sys.stderr.write('Lookup failed with: {}'.format(request))

הגדלת הזמן הקצוב לתפוגה ב-Cloud Storage

אם עומס העבודה שלכם מתקשר עם Cloud Storage, כדאי להגדיל את משך הזמן הקצוב לתפוגה על ידי הוספת ערכים של זמן קצוב לתפוגה אל /etc/environment. כדי לעשות זאת, מוסיפים את קטע הקוד הבא לקטע write_files: בהגדרות של cloud-init. קטע הקוד הזה יוצר סקריפט בשם /var/scripts/configure-gcs-timeouts.sh.

 - path: /var/scripts/configure-gcs-timeouts.sh
    permissions: 0444
    owner: root
    content: |
      echo "GCS_RESOLVE_REFRESH_SECS=60" >> /etc/environment
      echo "GCS_REQUEST_CONNECTION_TIMEOUT_SECS=300" >> /etc/environment
      echo "GCS_METADATA_REQUEST_TIMEOUT_SECS=300" >> /etc/environment
      echo "GCS_READ_REQUEST_TIMEOUT_SECS=300" >> /etc/environment
      echo "GCS_WRITE_REQUEST_TIMEOUT_SECS=600" >> /etc/environment

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