커스텀 OS 이미지 사용

TPU VM에 커스텀 OS 이미지를 사용하여 소프트웨어를 미리 로드하거나, 특정 OS 배포를 사용하거나, 커스텀 커널 수정을 적용할 수 있습니다. 커스텀 이미지를 만들려면 이미지 생성 프로세스 중에 특정 시스템을 수정하고 TPU 기능에 필요한 부팅 시간 작업을 처리하도록 이미지를 구성해야 합니다.

TPU에서 커스텀 OS 이미지를 사용하는 경우 다음 면책 조항에 유의하세요.

  • Google은 기본 TPU 최적화 Ubuntu 장기적 지원(LTS) 이미지를 제공합니다. 이 페이지에 나열된 OS 변경사항은 Google에서 지원하고 TPU에 최적화된 Ubuntu LTS 이미지에 대해서만 검증됩니다.
  • 다른 OS 배포 또는 커스텀 이미지에 필요한 OS 변경사항을 추정할 책임은 사용자에게 있습니다. Google은 이 페이지에 나열된 Ubuntu 수정사항이 다른 OS 배포판 또는 커스텀 커널이 있는 다른 Ubuntu 이미지에서 작동한다고 보장하지 않습니다.
  • Google은 기본 TPU 최적화 Ubuntu LTS 이미지 이외의 OS 이미지를 빌드하거나 테스트를 제공하지 않습니다. 커스텀 OS 이미지를 빌드하고 테스트해야 합니다.

기본 TPU 최적화 Ubuntu LTS 이미지에 대한 자세한 내용은 TPU OS 이미지를 참조하세요.

기본 요건

기본 이미지에 다음 구성요소가 설치되어 있어야 합니다.

  • Docker
  • ethtool, 버전 6.7 이상(TPU v6e 또는 TPU7x를 사용하는 경우)
  • PyYAML 및 requests 패키지가 포함된 Python 3(Ubuntu에서 python3-yaml 및 python3-requests로 사용 가능)
  • gcloud CLI

TPU7x VM의 경우 기본 이미지는 최소 게스트 OS Linux 커널 버전 6.8을 사용해야 합니다.

이미지 생성 중에 수정하기

커스텀 Ubuntu 이미지를 빌드하는 동안 다음 수정사항을 적용합니다.

무인 업그레이드 사용 중지

백그라운드 패키지 업데이트가 dpkg 잠금을 유지하거나 TPU 워크로드를 중단하지 않도록 unattended-upgrades를 사용 중지하고 삭제합니다.

echo 'APT::Periodic::Unattended-Upgrade "0";' > /etc/apt/apt.conf.d/99cloudtpuconf
DEBIAN_FRONTEND=noninteractive apt-get -y purge unattended-upgrades

TPU 기기를 VFIO에 바인딩

게스트 OS가 TPU 하드웨어에 액세스할 수 있도록 하려면 TPU 기기를 vfio-pci 드라이버에 바인딩해야 합니다.

  1. /etc/udev/rules.d/에 99-tpu-vfiopci.rules이라는 udev 규칙 파일을 만듭니다.

    # 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. /lib/udev/에 bind_to_vfio_pci.sh이라는 스크립트를 만듭니다.

    #!/bin/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_CMDLINE_LINUX에 다음 매개변수를 포함하도록 GRUB 구성(일반적으로 /etc/default/grub)을 업데이트합니다.

  • idle=poll: CPU가 저전력 유휴 상태로 전환되지 않도록 합니다.
  • transparent_hugepage=always: 투명한 대용량 페이지(THP)를 사용 설정합니다.
  • iommu.passthrough=0: 입력 출력 메모리 관리 단위(IOMMU) 패스 스루 모드를 사용 중지합니다.
  • panic=-1: 커널 패닉이 발생하면 커널이 즉시 재부팅되도록 구성합니다.
  • intel_iommu=on,sm_on: 확장 가능한 모드에서 Intel IOMMU를 사용 설정합니다. TPU7x 및 v5p 아키텍처에 필요합니다.

다음 단계에서는 이러한 커널 매개변수를 업데이트하는 방법을 보여줍니다.

  1. 공통 매개변수를 사용하여 커널 명령줄 변수를 설정합니다.

    kernel_cmdline="idle=poll transparent_hugepage=always iommu.passthrough=0 panic=-1"
    
  2. TPU7x 또는 TPU v5p용 이미지를 빌드하는 경우 Intel IOMMU 매개변수를 추가합니다.

    kernel_cmdline="${kernel_cmdline} intel_iommu=on,sm_on"
    
  3. /etc/default/grub에서 GRUB_CMDLINE_LINUX를 업데이트하고 GRUB 구성을 다시 생성합니다.

    sed -i "s/^GRUB_CMDLINE_LINUX=\".*\"/GRUB_CMDLINE_LINUX=\"${kernel_cmdline}\"/" /etc/default/grub
    echo "Status: New kernel cmdline: $(grep -e '^GRUB_CMDLINE_LINUX=' /etc/default/grub)"
    
    update-grub
    

vBar 에이전트 설치

칩 간 상호 연결(ICI) 네트워크가 작동하려면 vBar 에이전트가 필요합니다.

이미지 생성 중에 vBar 에이전트 컨테이너 이미지를 가져오려면 다음 명령어를 실행합니다.

  1. gcr.io로 Docker를 인증합니다.

    gcloud auth configure-docker gcr.io
    
  2. vBar 에이전트 Docker 이미지를 가져옵니다.

    docker pull gcr.io/cloud-tpu-v2-images/vbar_control_agent:0.0.3
    

부팅 시 vBar 에이전트 컨테이너를 시작하도록 이미지를 구성하는 방법을 알아보려면 vBar 에이전트 시작을 참조하세요.

선택사항: AI Telemetry Collector 설치 및 실행

AI 원격 분석 수집기는 TPU VM 내에서 실행되며 Cloud Monitoring 또는 자체 Prometheus 기반 모니터링 파이프라인을 통해 런타임 및 인프라 측정항목에 액세스할 수 있게 해줍니다. ai-telemetry-collector Docker 이미지를 사용하여 커스텀 OS에서 AI Telemetry Collector를 사용할 수 있습니다. 커스텀 OS에 이미지를 설치하고 config.yaml 파일을 사용하여 수집 간격을 지정하거나, 특정 측정항목을 사용 설정 또는 중지하거나, 내보내기 대상을 변경할 수 있습니다.

AI Telemetry Collector를 설치하고 실행하려면 다음 명령어를 실행합니다.

  1. gcr.io로 Docker를 인증합니다.

    gcloud auth configure-docker gcr.io
    
  2. AI Telemetry Collector Docker 이미지를 가져옵니다.

    docker pull gcr.io/cloud-tpu-v2-images/ai-telemetry-collector:latest
    
  3. 기본 구성으로 AI Telemetry Collector 이미지를 사용하여 컨테이너를 실행합니다.

    docker run -d --restart=always --privileged --net=host gcr.io/cloud-tpu-v2-images/ai-telemetry-collector:latest
    

    커스텀 구성 파일을 사용하거나 구성 파일을 추가하는 방법에 대한 자세한 내용은 AI Telemetry Collector를 참조하세요.

부팅 시간 수정

VM이 부팅될 때마다 다음 섹션의 작업을 수행하도록 이미지를 구성합니다. cloud-init 도구를 사용하여 메타데이터를 인스턴스에 전달하여 부팅 시간 작업을 구성할 수 있습니다. 다음 섹션의 구성에서는 write_files 및 runcmd와 같은 모듈을 사용합니다. 작성할 파일을 정의하는 스니펫은 write_files: 키 아래에 포함되어야 하며 부팅 시 실행해야 하는 명령어는 cloud-init 구성의 runcmd: 키 아래에 포함되어야 합니다.

vBar 에이전트 시작

부팅 시 vBar 제어 에이전트 컨테이너를 시작합니다.

docker run -d --restart=always --privileged --net=host \
  -e HOME=/home/tpu-runtime --name=vbar_control_agent \
  gcr.io/cloud-tpu-v2-images/vbar_control_agent:0.0.3 \
  vbar_control_agent_files/bin/vbar_control_agent --logtostderr

환경 변수 구성

TPU 워크로드에 맞게 환경이 올바르게 초기화되도록 하려면 시스템 부팅 프로세스 중에 Compute Engine 메타데이터 서버에서 런타임 구성 변수를 검색해야 합니다. 이렇게 하려면 cloud-init 구성의 write_files: 섹션에 다음 스니펫을 추가합니다. 그러면 /var/scripts/configure-env-vars.sh라는 스크립트가 생성됩니다. 이 스크립트는 tpu-env 메타데이터 키에서 속성 검색을 자동화하고 TPU 소프트웨어 스택에서 사용할 수 있도록 /home/tpu-runtime/tpu-env에 저장합니다.

write_files:
  - path: /var/scripts/configure-env-vars.sh
    permissions: '0755'
    owner: root
    content: |
      #!/bin/bash
      grep -q CLOUDSDK_PYTHON /etc/environment || echo "CLOUDSDK_PYTHON=/usr/bin/python3" >> /etc/environment
      export HOME=/home/tpu-runtime
      mkdir -p "${HOME}"
      curl -s 'http://metadata.google.internal/computeMetadata/v1/instance/attributes/tpu-env' -H 'Metadata-Flavor: Google' > /tmp/tpu-env.yaml
      python3 -c '
      import yaml
      with open("/tmp/tpu-env.yaml", "r") as stream_in:
        for k, v in (yaml.safe_load(stream_in) or {}).items():
          print("{var}={value}".format(var=k, value=str(v)))
      ' > "${HOME}/tpu-env"
      rm -f /tmp/tpu-env.yaml
      set -a
      source "${HOME}/tpu-env"
      set +a

VM 메타데이터 가져오기

다음 스니펫은 특정 인스턴스 속성 및 커스텀 메타데이터 태그에 대해 메타데이터 서버를 프로그래매틱 방식으로 쿼리하는 Python 유틸리티인 /var/scripts/get-vm-metadata.py라는 스크립트를 만듭니다. cloud-init 구성의 write_files: 섹션에 다음을 추가합니다.

write_files:
  - path: /var/scripts/get-vm-metadata.py
    permissions: '0755'
    owner: root
    content: |
      #!/usr/bin/env python3
      import os
      import sys
      import requests

      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.text)
      elif request.status_code in (403, 404):
        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에 제한 시간 값을 추가하여 제한 시간을 늘립니다. 이렇게 하려면 cloud-init 구성의 write_files: 섹션에 다음 스니펫을 추가합니다. 그러면 /var/scripts/configure-gcs-timeouts.sh라는 스크립트가 생성됩니다.

write_files:
  - path: /var/scripts/configure-gcs-timeouts.sh
    permissions: '0755'
    owner: root
    content: |
      #!/bin/bash
      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

네트워크 설정 구성

TPU VM에서 네트워크 처리량과 지연 시간을 최적화하려면 cloud-init 구성의 write_files: 섹션에 다음 스크립트를 추가합니다. TPU v6e 및 TPU7x VM에서 이러한 스크립트는 TCP 데이터 분할, TCP 수신 버퍼 한도 증가, NUMA 코어 간 NIC 인터럽트 분산도 사용 설정합니다. 이러한 스크립트에는 VM 메타데이터 가져오기 섹션의 /var/scripts/get-vm-metadata.py 스크립트가 필요합니다.

write_files:
  - path: /var/scripts/configure-networking.sh
    permissions: '0755'
    owner: root
    content: |
      #!/bin/bash
      ACCELERATOR_TYPE=$(/usr/bin/python3 /var/scripts/get-vm-metadata.py accelerator-type)
      if [[ "$ACCELERATOR_TYPE" =~ ^v6e ]] || [[ "$ACCELERATOR_TYPE" =~ ^tpu7 ]]; then
        echo "Enabling max rx buffer size for v6e, tpu7x & tpu7"
        for port in $(ls /sys/class/net | grep ens); do
          dhclient -r "$port"
          if ethtool -h | grep -q tcp-data-split; then
            if ethtool -G "$port" tcp-data-split on; then
              echo "Max RX buffer size and TCP data split enabled for $port"
            else
              echo "Unable to enable max RX buffer size and TCP data split for $port"
            fi
          else
            if ethtool --set-priv-flags "$port" enable-max-rx-buffer-size on; then
              echo "Max RX buffer size enabled for $port"
            else
              echo "Unable to enable max RX buffer size for $port"
            fi
          fi
          dhclient "$port"
        done
      else
        echo "Skipping max rx buffer size for TPU generation $ACCELERATOR_TYPE before v6e"
      fi

  - path: /var/scripts/set_tcp_config.sh
    permissions: '0755'
    owner: root
    content: |
      #!/bin/bash
      echo "Setting TCP config"

      # Disable metrics cache
      sysctl -w net.ipv4.tcp_no_metrics_save=1

      # Disable slow start after idle
      sysctl -w net.ipv4.tcp_slow_start_after_idle=0

      # Set rto_min 5ms and enable quickack
      sysctl_rto_min_exists=$(sysctl -a | grep tcp_rto_min_us)
      if [[ -z "$sysctl_rto_min_exists" ]]; then
        ip route show | while IFS= read -r route; do
          if ! echo "$route" | grep -q "linkdown"; then
            ip route change ${route/lock/} rto_min 5ms quickack 1
          fi
        done
      else
        sysctl -w net.ipv4.tcp_rto_min_us=5000
        ip route show | while IFS= read -r route; do
          if ! echo "$route" | grep -q "linkdown"; then
            ip route change ${route/lock/} quickack 1
          fi
        done
      fi

      # Increase TCP zerocopy control memory
      sysctl -w net.core.optmem_max=131072

      # Disable Cubic Hystart Ack-Train
      echo 2 > /sys/module/tcp_cubic/parameters/hystart_detect

      ACCELERATOR_TYPE=$(/usr/bin/python3 /var/scripts/get-vm-metadata.py accelerator-type)
      if [[ "$ACCELERATOR_TYPE" =~ ^v6e ]] || [[ "$ACCELERATOR_TYPE" =~ ^tpu7 ]]; then
        # Increase TCP receive buffer limits
        echo "4096 41943040 314572800" > /proc/sys/net/ipv4/tcp_rmem
      fi

  - path: /var/scripts/spread-nic-irqs.sh
    permissions: '0755'
    owner: root
    content: |
      #!/bin/bash
      ACCELERATOR_TYPE=$(/usr/bin/python3 /var/scripts/get-vm-metadata.py accelerator-type)
      if [[ "$ACCELERATOR_TYPE" == v6e* ]] || [[ "$ACCELERATOR_TYPE" == tpu7* ]]; then
        node0_cores=$(echo /sys/devices/system/node/node0/cpu[0-9]* | wc -w)
        ht_buddies=$(cat /sys/devices/system/cpu/cpu0/topology/core_cpus_list | tr ',' ' ' | wc -w)
        total_schedulable_cores=$((node0_cores / ht_buddies))
        second_numa_starting_core=${total_schedulable_cores}

        first_numa_core=0
        second_numa_core=${second_numa_starting_core}
        for nic in $(ls -1 /sys/class/net); do
          if [[ -d "/sys/class/net/$nic/device" ]]; then
            NUM_QUEUES=$(echo /sys/class/net/"$nic"/queues/tx* | wc -w)
            base_ntfy_block=$(ls -1 /sys/class/net/"${nic}"/device/msi_irqs | sort -n | head -n 1)
            num_ntfy_blocks=$(ls /sys/class/net/"${nic}"/device/msi_irqs/ | wc -w)
            tx_irq_base_directory=$((base_ntfy_block))
            rx_irq_base_directory=$((base_ntfy_block + (num_ntfy_blocks / 2)))

            core=0
            max_core=${total_schedulable_cores}
            nic_numa=$(cat /sys/class/net/"${nic}"/device/numa_node)
            if [[ "${nic_numa}" -eq "1" ]]; then
              core=${second_numa_core}
              max_core=$((second_numa_starting_core + total_schedulable_cores))
            else
              core=${first_numa_core}
            fi

            for ((queue = 0; queue < $NUM_QUEUES; queue++)); do
              echo $core > /proc/irq/$((tx_irq_base_directory + $queue))/smp_affinity_list
              echo $core > /proc/irq/$((rx_irq_base_directory + $queue))/smp_affinity_list
              cp /proc/irq/$((tx_irq_base_directory + $queue))/smp_affinity /sys/class/net/"$nic"/queues/tx-"$queue"/xps_cpus
              core=$((core + 1))
              if [[ $core -ge $max_core ]]; then
                if [[ "${nic_numa}" -eq "1" ]]; then
                  core=${second_numa_starting_core}
                else
                  core=0
                fi
              fi
            done

            if [[ "${nic_numa}" -eq "1" ]]; then
              second_numa_core=${core}
            else
              first_numa_core=${core}
            fi
          fi
        done
      else
        echo "Skipping NIC IRQ spreading for TPU generation $ACCELERATOR_TYPE before v6e"
      fi

부팅 스크립트 실행

VM이 부팅될 때 구성 스크립트를 자동으로 실행하려면 cloud-init 구성에 runcmd: 섹션을 추가합니다.

runcmd:
  - /var/scripts/configure-env-vars.sh
  - /var/scripts/configure-gcs-timeouts.sh
  - /var/scripts/configure-networking.sh
  - /var/scripts/set_tcp_config.sh
  - /var/scripts/spread-nic-irqs.sh

다음 단계