Implementa un sistema de inferencia de TensorFlow escalable

Last reviewed 2023-11-02 UTC

En este documento, se describe cómo implementar la arquitectura de referencia que se describe en Sistema de inferencia de TensorFlow escalable.

Esta serie está dirigida a desarrolladores familiarizados con Google Kubernetes Engine y con los marcos de trabajo de aprendizaje automático (AA), incluidos TensorFlow y NVIDIA TensorRT.

Después de completar esta implementación, consulta Mide y ajusta el rendimiento de un sistema de inferencia de TensorFlow.

Arquitectura

En el siguiente diagrama, se muestra la arquitectura del sistema de inferencia.

Arquitectura del sistema de inferencia

Cloud Load Balancing envía el tráfico de la solicitud al clúster de GKE más cercano. El clúster contiene un Pod para cada nodo. En cada Pod, un Triton Inference Server proporciona un servicio de inferencia para entregar modelos ResNet-50 y una GPU NVIDIA T4 mejora el rendimiento. Los servidores de supervisión en el clúster recopilan datos de métricas sobre el uso de GPU y el uso de la memoria.

Para obtener más detalles, consulta Sistema de inferencia de TensorFlow escalable.

Objetivos

  • Descargar un modelo previamente entrenado ResNet-50 y usa la integración de TensorFlow con TensorRT (TF-TRT) para aplicar las optimizaciones
  • Entregar un modelo ResNet-50 desde un NVIDIA Triton Inference Server
  • Compilar un sistema de supervisión para Triton mediante Prometheus y Grafana
  • Compilar una herramienta de prueba de carga mediante Locust

Costos

Además de la GPU de NVIDIA T4, en esta implementación, usas 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.

Cuando termines esta implementación, no borres los recursos que creaste. Necesitas estos recursos cuando medes y ajustas la implementación.

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. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyectos

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  4. Habilita la API de GKE.

    Habilita la API

  5. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyectos

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

  7. Habilita la API de GKE.

    Habilita la API

Compila modelos optimizados con TF-TRT

En esta sección, crearás un entorno de trabajo y optimizarás el modelo previamente entrenado.

El modelo previamente entrenado usa el conjunto de datos falso en gs://cloud-tpu-test-datasets/fake_imagenet/. También hay una copia del modelo previamente entrenado en la ubicación de Cloud Storage en gs://solutions-public-assets/tftrt-tutorial/resnet/export/1584366419/.

Crea un entorno de trabajo

Para tu entorno de trabajo, crea una instancia de Compute Engine mediante Deep Learning VM Images. Debes optimizar y cuantificar el modelo ResNet-50 con TensorRT en esta instancia.

  1. En la consola de Google Cloud, activa Cloud Shell.

    Activar Cloud Shell

  2. Implementa una instancia llamada working-vm:

    gcloud config set project PROJECT_ID
    gcloud config set compute/zone us-west1-b
    gcloud compute instances create working-vm \
        --scopes cloud-platform \
        --image-family common-cu113 \
        --image-project deeplearning-platform-release \
        --machine-type n1-standard-8 \
        --min-cpu-platform="Intel Skylake" \
        --accelerator=type=nvidia-tesla-t4,count=1 \
        --boot-disk-size=200GB \
        --maintenance-policy=TERMINATE \
        --metadata="install-nvidia-driver=True"
    

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud que creaste antes.

    Este comando inicia una instancia de Compute Engine con NVIDIA T4. En el primer inicio, instala automáticamente el controlador de GPU de NVIDIA que es compatible con TensorRT 5.1.5.

Crea archivos del modelo con diferentes optimizaciones

En esta sección, aplicarás las siguientes optimizaciones al modelo ResNet-50 original mediante TF-TRT:

  • Optimización de grafos
  • Conversión a FP16 con la optimización del grafo
  • Cuantización con INT8 con la optimización del grafo

Para obtener detalles sobre estas optimizaciones, consulta Optimización del rendimiento.

  1. En la consola de Google Cloud, ve a Compute Engine > Instancias de VM.

    Ir a Instancias de VM

    Verás la instancia working-vm que creaste antes.

  2. Para abrir la consola de la terminal de la instancia, haz clic en SSH.

    Usa esta terminal para ejecutar el resto de los comandos de este documento.

  3. En la terminal, clona el repositorio requerido y cambia el directorio actual:

    cd $HOME
    git clone https://github.com/GoogleCloudPlatform/gke-tensorflow-inference-system-tutorial
    cd gke-tensorflow-inference-system-tutorial/server
    
  4. Descarga el modelo ResNet-50 previamente entrenado en un directorio local:

    mkdir -p models/resnet/original/00001
    gsutil cp -R gs://solutions-public-assets/tftrt-tutorial/resnet/export/1584366419/* models/resnet/original/00001
    
  5. Compila una imagen de contenedor que contenga herramientas de optimización para TF-TRT:

    docker build ./ -t trt-optimizer
    docker image list
    

    El último comando muestra una tabla de repositorios.

  6. En la tabla, en la fila del repositorio tft-optimizer, copia el ID de la imagen.

  7. Aplica las optimizaciones (optimización de grafos, conversión a FP16 y cuantización con INT8) al modelo original:

    export IMAGE_ID=IMAGE_ID
    
    nvidia-docker run --rm \
        -v `pwd`/models/:/workspace/models ${IMAGE_ID} \
        --input-model-dir='models/resnet/original/00001' \
        --output-dir='models/resnet' \
        --precision-mode='FP32' \
        --batch-size=64
    
    nvidia-docker run --rm \
        -v `pwd`/models/:/workspace/models ${IMAGE_ID} \
        --input-model-dir='models/resnet/original/00001' \
        --output-dir='models/resnet' \
        --precision-mode='FP16' \
        --batch-size=64
    
    nvidia-docker run --rm \
        -v `pwd`/models/:/workspace/models ${IMAGE_ID} \
        --input-model-dir='models/resnet/original/00001' \
        --output-dir='models/resnet' \
        --precision-mode='INT8' \
        --batch-size=64 \
        --calib-image-dir='gs://cloud-tpu-test-datasets/fake_imagenet/' \
        --calibration-epochs=10
    

    Reemplaza IMAGE_ID por el ID de la imagen de tft-optimizer que copiaste en el paso anterior.

    La opción --calib-image-dir especifica la ubicación de los datos de entrenamiento que se usan para el modelo previamente entrenado. Se usan los mismos datos de entrenamiento para una calibración de la cuantización con INT8. El proceso de calibración puede tomar alrededor de 5 minutos.

    Cuando los comandos terminen de ejecutarse, la última línea de resultado será similar a la siguiente, en la que los modelos optimizados se guardan en ./models/resnet:

    INFO:tensorflow:SavedModel written to: models/resnet/INT8/00001/saved_model.pb
    

    La estructura de directorios es similar a la siguiente:

    models
    └── resnet
        ├── FP16
        │   └── 00001
        │       ├── saved_model.pb
        │       └── variables
        ├── FP32
        │   └── 00001
        │       ├── saved_model.pb
        │       └── variables
        ├── INT8
        │   └── 00001
        │       ├── saved_model.pb
        │       └── variables
        └── original
            └── 00001
                ├── saved_model.pb
                └── variables
                    ├── variables.data-00000-of-00001
                    └── variables.index
    

En la siguiente tabla, se resume la relación entre los directorios y las optimizaciones.

Directorio Optimización
FP16 Conversión a FP16, además de la optimización del grafo
FP32 Optimización de grafos
INT8 Cuantización con INT8, además de la optimización del grafo
original Modelo original (sin optimización con TF-TRT)

Implementa un servidor de inferencia

En esta sección, implementarás servidores de Triton con cinco modelos. En primer lugar, debes subir el objeto binario del modelo que creaste en la sección anterior a Cloud Storage. Luego, crearás un clúster de GKE y, luego, implementarás servidores Triton en él.

Sube el objeto binario del modelo

  • En la terminal SSH, sube los objetos binarios del modelo y los archivos de configuración config.pbtxt a un bucket de almacenamiento:

    export PROJECT_ID=PROJECT_ID
    export BUCKET_NAME=${PROJECT_ID}-models
    
    mkdir -p original/1/model/
    cp -r models/resnet/original/00001/* original/1/model/
    cp original/config.pbtxt original/1/model/
    cp original/imagenet1k_labels.txt original/1/model/
    
    mkdir -p tftrt_fp32/1/model/
    cp -r models/resnet/FP32/00001/* tftrt_fp32/1/model/
    cp tftrt_fp32/config.pbtxt tftrt_fp32/1/model/
    cp tftrt_fp32/imagenet1k_labels.txt tftrt_fp32/1/model/
    
    mkdir -p tftrt_fp16/1/model/
    cp -r models/resnet/FP16/00001/* tftrt_fp16/1/model/
    cp tftrt_fp16/config.pbtxt tftrt_fp16/1/model/
    cp tftrt_fp16/imagenet1k_labels.txt tftrt_fp16/1/model/
    
    mkdir -p tftrt_int8/1/model/
    cp -r models/resnet/INT8/00001/* tftrt_int8/1/model/
    cp tftrt_int8/config.pbtxt tftrt_int8/1/model/
    cp tftrt_int8/imagenet1k_labels.txt tftrt_int8/1/model/
    
    mkdir -p tftrt_int8_bs16_count4/1/model/
    cp -r models/resnet/INT8/00001/* tftrt_int8_bs16_count4/1/model/
    cp tftrt_int8_bs16_count4/config.pbtxt tftrt_int8_bs16_count4/1/model/
    cp tftrt_int8_bs16_count4/imagenet1k_labels.txt tftrt_int8_bs16_count4/1/model/
    
    gsutil mb gs://${BUCKET_NAME}
    gsutil -m cp -R original tftrt_fp32 tftrt_fp16 tftrt_int8 tftrt_int8_bs16_count4 \
        gs://${BUCKET_NAME}/resnet/
    

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud que creaste antes.

    Los siguientes parámetros de ajuste se especifican en los archivos config.pbtxt:

    • Nombre del modelo
    • Nombre del tensor de entrada y de salida
    • Asignación de GPU a cada modelo
    • Tamaño del lote y cantidad de grupos de instancias

    Por ejemplo, el archivo original/1/model/config.pbtxt incluye el siguiente contenido:

    name: "original"
    platform: "tensorflow_savedmodel"
    max_batch_size: 64
    input {
        name: "input"
        data_type: TYPE_FP32
        format: FORMAT_NHWC
        dims: [ 224, 224, 3 ]
    }
    output {
        name: "probabilities"
        data_type: TYPE_FP32
        dims: 1000
        label_filename: "imagenet1k_labels.txt"
    }
    default_model_filename: "model"
    instance_group [
      {
        count: 1
        kind: KIND_GPU
      }
    ]
    dynamic_batching {
      preferred_batch_size: [ 64 ]
      max_queue_delay_microseconds: 20000
    }
    

Para obtener información detallada sobre el tamaño del lote y la cantidad de grupos de instancias, consulta Optimización del rendimiento.

En la siguiente tabla, se resumen los cinco modelos que implementaste en esta sección.

Nombre del modelo Optimización
original Modelo original (sin optimización con TF-TRT)
tftrt_fp32 Optimización de grafos
(tamaño del lote=64, grupos de instancias=1)
tftrt_fp16 La conversión a FP16, además de la optimización del grafo
(tamaño de lote=64, grupos de instancias=1)
tftrt_int8 Cuantización con INT8 además de la optimización del grafo
(tamaño del lote=64, grupos de instancias=1)
tftrt_int8_bs16_count4 Cuantización con INT8, además de la optimización de grafos
(tamaño de lote =16, grupos de instancias=4)

Implementa servidores de inferencia mediante Triton

  1. En la terminal de SSH, instala y configura el paquete de autenticación, que administra los clústeres de GKE:

    export USE_GKE_GCLOUD_AUTH_PLUGIN=True
    sudo apt-get install google-cloud-sdk-gke-gcloud-auth-plugin
    
  2. Crea un clúster de GKE y un grupo de nodos de GPU con nodos de procesamiento que usen una GPU NVIDIA T4:

    gcloud auth login
    gcloud config set compute/zone us-west1-b
    gcloud container clusters create tensorrt-cluster \
        --num-nodes=20
    gcloud container node-pools create t4-gpu-pool \
        --num-nodes=1 \
        --machine-type=n1-standard-8 \
        --cluster=tensorrt-cluster \
        --accelerator type=nvidia-tesla-t4,count=1
    

    La marca --num-nodes especifica 20 instancias para el clúster de GKE y una instancia para el grupo de nodos de GPU t4-gpu-pool.

    El grupo de nodos de GPU consta de una única instancia de n1-standard-8 con una GPU NVIDIA T4. La cantidad de instancias de GPU debe ser igual o mayor que la cantidad de Pods del servidor de inferencia porque la GPU NVIDIA T4 no puede compartirse por varios Pods en la misma instancia.

  3. Muestra la información del clúster:

    gcloud container clusters list
    

    El resultado es similar al siguiente:

    NAME              LOCATION    MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION    NUM_NODES  STATUS
    tensorrt-cluster  us-west1-b  1.14.10-gke.17  XX.XX.XX.XX    n1-standard-1  1.14.10-gke.17  21         RUNNING
    
  4. Muestra la información del grupo de nodos:

    gcloud container node-pools list --cluster tensorrt-cluster
    

    El resultado es similar al siguiente:

    NAME          MACHINE_TYPE   DISK_SIZE_GB  NODE_VERSION
    default-pool  n1-standard-1  100           1.14.10-gke.17
    t4-pool       n1-standard-8  100           1.14.10-gke.17
    
  5. Habilita la carga de trabajo daemonSet:

    gcloud container clusters get-credentials tensorrt-cluster
    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/master/nvidia-driver-installer/cos/daemonset-preloaded.yaml
    

    Este comando carga el controlador de GPU de NVIDIA en los nodos del grupo de nodos de GPU. También carga automáticamente el controlador cuando agregas un nodo nuevo al grupo de nodos de GPU.

  6. Implementa servidores de inferencia en el clúster:

    sed -i.bak "s/YOUR-BUCKET-NAME/${PROJECT_ID}-models/" trtis_deploy.yaml
    kubectl create -f trtis_service.yaml
    kubectl create -f trtis_deploy.yaml
    
  7. Espera unos minutos hasta que los servicios estén disponibles.

  8. Obtén la dirección clusterIP de Triton y almacénala en una variable de entorno:

    export TRITON_IP=$(kubectl get svc inference-server \
      -o "jsonpath={.spec['clusterIP']}")
    echo ${TRITON_IP}
    

En este punto, el servidor de inferencia está entregando cuatro modelos ResNet-50 que creaste en la sección Crea archivos de modelo con diferentes optimizaciones. Los clientes pueden especificar el modelo que se usará cuando se envíen solicitudes de inferencia.

Implementa servidores de supervisión con Prometheus y Grafana

  1. En la terminal SSH, implementa servidores de Prometheus en el clúster:

    sed -i.bak "s/CLUSTER-IP/${TRITON_IP}/" prometheus-configmap.yml
    kubectl create namespace monitoring
    kubectl apply -f prometheus-service.yml -n monitoring
    kubectl create -f clusterRole.yml
    kubectl create -f prometheus-configmap.yml -n monitoring
    kubectl create -f prometheus-deployment.yml -n monitoring
    
  2. Obtén la URL de extremo del servicio de Prometheus.

    ip_port=$(kubectl get svc prometheus-service \
      -o "jsonpath={.spec['clusterIP']}:{.spec['ports'][0]['port']}" -n monitoring)
    echo "http://${ip_port}"
    

    Toma nota de la URL del extremo de Prometheus, ya que la usarás para configurar Grafana más adelante.

  3. Implementa servidores de Grafana en el clúster:

    kubectl create -f grafana-service.yml -n monitoring
    kubectl create -f grafana-deployment.yml -n monitoring
    
  4. Espera unos minutos hasta que todos los servicios estén disponibles.

  5. Obtén la URL de extremo del servicio de Grafana.

    ip_port=$(kubectl get svc grafana-service \
      -o "jsonpath={.status['loadBalancer']['ingress'][0]['ip']}:{.spec['ports'][0]['port']}" -n monitoring)
    echo "http://${ip_port}"
    

    Toma nota de la URL del extremo de Grafana para usarla en el siguiente paso.

  6. En un navegador web, ve a la URL de Grafana que anotaste en el paso anterior.

  7. Accede con el ID de usuario y la contraseña predeterminados (admin y admin). Cuando se te solicite, cambia la contraseña predeterminada.

  8. Haz clic en Agrega tu primera fuente de datos y, en la lista Bases de datos de series temporales, selecciona Prometheus.

  9. En la pestaña Configuración, en el campo URL, ingresa la URL del extremo de Prometheus que anotaste antes.

  10. Haz clic en Guardar y probar y, luego, regresa a la pantalla principal.

  11. Agrega una métrica de supervisión para nv_gpu_utilization:

    1. Haz clic en Crea tu primer panel y, luego, en Agregar visualización.
    2. En la lista Fuente de datos, selecciona Prometheus.
    3. En la pestaña Consulta, ingresa nv_gpu_utilization en el campo Métrica.

    4. En la sección Opciones de panel, en el campo Título, ingresa GPU Utilization y, luego, haz clic en Aplicar.

      En la página, se muestra un panel sobre el uso de GPU.

  12. Agrega una métrica de supervisión para nv_gpu_memory_used_bytes:

    1. Haz clic en Agregar y selecciona Visualización.
    2. En la pestaña Consulta, ingresa nv_gpu_memory_used_bytes en el campo Métrica.

    3. En la sección Opciones de panel, en el campo Título, ingresa GPU Memory Used y, luego, haz clic en Guardar.

  13. Para agregar el panel, en el panel Guardar panel, haz clic en Guardar.

    Verás los grafos para el uso de GPU y la memoria de GPU usadas.

Implementa una herramienta de prueba de carga

En esta sección, implementarás la herramienta de prueba de carga de Locust en GKE y generarás cargas de trabajo para medir el rendimiento de los servidores de inferencia.

  1. En la terminal de SSH, compila una imagen de Docker que contenga bibliotecas cliente de Triton y súbela a Container Registry:

    cd ../client
    git clone https://github.com/triton-inference-server/server
    cd server
    git checkout r19.05
    sed -i.bak "s/bootstrap.pypa.io\/get-pip.py/bootstrap.pypa.io\/pip\/2.7\/get-pip.py/" Dockerfile.client
    docker build -t tritonserver_client -f Dockerfile.client .
    gcloud auth configure-docker
    docker tag tritonserver_client \
        gcr.io/${PROJECT_ID}/tritonserver_client
    docker push gcr.io/${PROJECT_ID}/tritonserver_client
    

    El proceso de compilación puede tomar alrededor de 5 minutos. Cuando se complete el proceso, aparecerá un símbolo del sistema en la terminal SSH.

  2. Cuando finalice el proceso de compilación, compila una imagen de Docker para generar una carga de trabajo de prueba y súbela a Container Registry:

    cd ..
    sed -i.bak "s/YOUR-PROJECT-ID/${PROJECT_ID}/" Dockerfile
    docker build -t locust_tester -f Dockerfile .
    docker tag locust_tester gcr.io/${PROJECT_ID}/locust_tester
    docker push gcr.io/${PROJECT_ID}/locust_tester
    

    No cambies ni reemplaces YOUR-PROJECT-ID en los comandos.

    Esta imagen se compiló a partir de la imagen que creaste en el paso anterior.

  3. Implementa los archivos service_master.yaml y deployment_master.yaml de Locust:

    sed -i.bak "s/YOUR-PROJECT-ID/${PROJECT_ID}/" deployment_master.yaml
    sed -i.bak "s/CLUSTER-IP-TRTIS/${TRITON_IP}/" deployment_master.yaml
    
    kubectl create namespace locust
    kubectl create configmap locust-config --from-literal model=original --from-literal saddr=${TRITON_IP} --from-literal rps=10 -n locust
    
    kubectl apply -f service_master.yaml -n locust
    kubectl apply -f deployment_master.yaml -n locust
    

    El recurso configmap se usa para especificar el modelo de aprendizaje automático al que los clientes envían solicitudes de inferencia.

  4. Espera unos minutos hasta que los servicios estén disponibles.

  5. Obtén la dirección clusterIP del cliente locust-master y almacénala en una variable de entorno:

    export LOCUST_MASTER_IP=$(kubectl get svc locust-master -n locust \
        -o "jsonpath={.spec['clusterIP']}")
    echo ${LOCUST_MASTER_IP}
    
  6. Implementa el cliente Locust:

    sed -i.bak "s/YOUR-PROJECT-ID/${PROJECT_ID}/" deployment_slave.yaml
    sed -i.bak "s/CLUSTER-IP-LOCUST-MASTER/${LOCUST_MASTER_IP}/" deployment_slave.yaml
    kubectl apply -f deployment_slave.yaml -n locust
    

    Estos comandos implementan 10 Pods de cliente de Locust que puedes usar para generar cargas de trabajo de prueba. Si no puedes generar suficientes solicitudes con la cantidad actual de clientes, puedes cambiar la cantidad de Pods con el siguiente comando:

    kubectl scale deployment/locust-slave --replicas=20 -n locust
    

    Cuando no haya suficiente capacidad para que un clúster predeterminado aumente la cantidad de réplicas, te recomendamos que aumentes la cantidad de nodos en el clúster de GKE.

  7. Copia la URL de la consola de Locust y, luego, abre esta URL en un navegador web:

    export LOCUST_IP=$(kubectl get svc locust-master -n locust \
         -o "jsonpath={.status.loadBalancer.ingress[0].ip}")
    echo "http://${LOCUST_IP}:8089"
    

    Se abrirá la consola de Locust y podrás generar cargas de trabajo de prueba a partir de ella.

Verifica los Pods en ejecución

Para asegurarte de que los componentes se implementen de forma correcta, verifica que los Pods se estén ejecutando.

  1. En la terminal de SSH, verifica el Pod del servidor de inferencia:

    kubectl get pods
    

    El resultado es similar al siguiente:

    NAME                                READY   STATUS    RESTARTS   AGE
    inference-server-67786cddb4-qrw6r   1/1     Running   0          83m
    

    Si no obtienes el resultado esperado, asegúrate de que completaste los pasos en Implementa servidores de inferencia mediante Triton.

  2. Verifica los Pods de Locust:

    kubectl get pods -n locust
    

    El resultado es similar al siguiente:

    NAME                                READY   STATUS    RESTARTS   AGE
    locust-master-75f6f6d4bc-ttllr      1/1     Running   0          10m
    locust-slave-76ddb664d9-8275p       1/1     Running   0          2m36s
    locust-slave-76ddb664d9-f45ww       1/1     Running   0          2m36s
    locust-slave-76ddb664d9-q95z9       1/1     Running   0          2m36s
    

    Si no obtienes el resultado esperado, asegúrate de que completaste los pasos en Implementa una herramienta de prueba de carga.

  3. Verifica los Pods de supervisión:

    kubectl get pods -n monitoring
    

    El resultado es similar al siguiente:

    NAME                                     READY   STATUS    RESTARTS   AGE
    grafana-deployment-644bbcb84-k6t7v       1/1     Running   0          79m
    prometheus-deployment-544b9b9f98-hl7q8   1/1     Running   0          81m
    

    Si no obtienes el resultado esperado, asegúrate de que completaste los pasos en Implementa servidores de supervisión con Prometheus y Grafana.

En la siguiente parte de esta serie, deberás usar este sistema de servidores de inferencia para conocer cómo varias optimizaciones mejoran el rendimiento y cómo interpretarlas. Para conocer los próximos pasos, consulta Mide y ajusta el rendimiento de un sistema de inferencia de TensorFlow.

¿Qué sigue?