Implantar um sistema de inferência do TensorFlow escalonável

Last reviewed 2023-11-02 UTC

Neste documento, descrevemos como implantar a arquitetura de referência descrita em Sistema de inferência escalonável do TensorFlow.

Esta série é destinada a desenvolvedores familiarizados com o Google Kubernetes Engine e os frameworks de machine learning (ML), incluindo o TensorFlow e a NVIDIA TensorRT.

Depois de concluir a implantação, consulte Analisar e ajustar o desempenho de um sistema de inferência do TensorFlow.

Arquitetura

O diagrama a seguir mostra a arquitetura do sistema de inferência.

Arquitetura do sistema de inferência.

O Cloud Load Balancing envia o tráfego de solicitação ao cluster do GKE mais próximo. O cluster contém um pod para cada nó. Em cada pod, um servidor de inferência Triton fornece um serviço de inferência (para exibir modelos ResNet-50), e uma GPU NVIDIA T4 melhora o desempenho. Os servidores de monitoramento no cluster coletam dados de métricas sobre a utilização da GPU e da memória.

Para saber mais detalhes, consulte Sistema de inferência do TensorFlow escalonável.

Objetivos

  • Faça o download de um modelo ResNet-50 pré-treinado e use a integração do TensorFlow com a TensorRT (TF-TRT) para aplicar otimizações.
  • Exibir um modelo ResNet-50 de um servidor de inferência NVIDIA Triton
  • Crie um sistema de monitoramento para o Triton usando o Prometheus e o Grafana.
  • Crie uma ferramenta de teste de carga usando o Locust.

Custos

Além da GPU NVIDIA T4, nesta implantação, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços.

Ao concluir este tutorial, não exclua os recursos que você criou. Você precisa desses recursos ao medir e ajustar a implantação.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. Ative a API GKE.

    Ative a API

  5. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  6. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  7. Ative a API GKE.

    Ative a API

Criar modelos otimizados com TF-TRT

Nesta seção, você vai criar um ambiente de trabalho e otimizar o modelo pré-treinado.

O modelo pré-treinado usa o conjunto de dados simulado em gs://cloud-tpu-test-datasets/fake_imagenet/. Há também uma cópia do modelo pré-treinado no local do Cloud Storage em gs://solutions-public-assets/tftrt-tutorial/resnet/export/1584366419/.

Criar um ambiente de trabalho

Para seu ambiente de trabalho, você cria uma instância do Compute Engine usando imagens de VM de aprendizado profundo. Otimize e quantize o modelo ResNet-50 com a TensorRT nessa instância.

  1. No Console do Google Cloud, ative o Cloud Shell.

    Ativar o Cloud Shell

  2. Implante uma instância chamada 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"
    

    Substitua PROJECT_ID pelo ID do projeto do Cloud que você criou anteriormente.

    Este comando inicia uma instância do Compute Engine usando o NVIDIA T4. Na primeira inicialização, ele instala automaticamente o driver da GPU NVIDIA compatível com o TensorRT 5.1.5.

Criar arquivos de modelo com diferentes otimizações

Nesta seção, você vai aplicar as seguintes otimizações ao modelo original do ResNet-50 usando o TF-TRT:

  • otimização de gráficos
  • Conversão para FP16 com a otimização de gráficos
  • Quantização com INT8 com a otimização de gráficos

Para saber mais sobre essas otimizações, consulte Otimização de desempenho.

  1. No Console do Google Cloud, selecione Compute Engine > instâncias da VM.

    Acessar instâncias de VM

    Você verá a instância working-vm criada anteriormente.

  2. Para abrir o console do terminal da instância, clique em SSH.

    Use esse terminal para executar o restante dos comandos neste documento.

  3. No terminal, clone o repositório necessário e altere o diretório atual:

    cd $HOME
    git clone https://github.com/GoogleCloudPlatform/gke-tensorflow-inference-system-tutorial
    cd gke-tensorflow-inference-system-tutorial/server
    
  4. Faça o download do modelo ResNet-50 pré-treinado para um diretório local:

    mkdir -p models/resnet/original/00001
    gsutil cp -R gs://solutions-public-assets/tftrt-tutorial/resnet/export/1584366419/* models/resnet/original/00001
    
  5. Crie uma imagem de contêiner que contenha ferramentas de otimização para o TF-TRT:

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

    O último comando mostra uma tabela de repositórios.

  6. Na tabela, na linha do repositório tft-optimizer, copie o ID da imagem.

  7. Aplique as otimizações (otimização de gráfico, conversão para FP16 e quantização com INT8) ao 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
    

    Substitua IMAGE_ID pelo ID da imagem para tft-optimizer que você copiou na etapa anterior.

    A opção --calib-image-dir especifica o local dos dados de treinamento que são usados para o modelo pré-treinado. Os mesmos dados de treinamento são usados na calibração para a quantização INT8. O processo de calibração pode levar cerca de 5 minutos.

    Quando os comandos terminam de ser executados, a última linha de saída é semelhante à seguinte, em que os modelos otimizados são salvos em ./models/resnet:

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

    A estrutura de diretórios é semelhante a esta:

    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
    

A tabela a seguir resume a relação entre diretórios e otimizações.

Diretório Otimização
FP16 Conversão para FP16, além da otimização do gráfico
FP32 otimização de gráficos
INT8 Quantização com INT8, além da otimização do gráfico
original Modelo original (sem otimização com TF-TRT)

Implantar um servidor de inferência

Nesta seção, você implanta servidores do Triton com cinco modelos. Primeiro, faça upload do binário do modelo que você criou na seção anterior para o Cloud Storage. Depois, crie um cluster do GKE e implante servidores do Triton no cluster.

Fazer upload do binário do modelo

  • No terminal SSH, faça upload dos binários do modelo e dos arquivos de configuração config.pbtxt para um bucket de armazenamento:

    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/
    

    Substitua PROJECT_ID pelo ID do projeto do Cloud que você criou anteriormente.

    Os seguintes parâmetros de ajuste são especificados nos arquivos config.pbtxt:

    • Nome do modelo
    • Nome do tensor de entrada e de saída
    • Alocação de GPU para cada modelo
    • Tamanho do lote e número de grupos de instâncias

    Por exemplo, o arquivo original/1/model/config.pbtxt tem o seguinte conteúdo:

    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 detalhes sobre o tamanho do lote e o número de grupos de instâncias, consulte Otimização de desempenho.

A tabela a seguir resume os cinco modelos implantados nesta seção.

Nome do modelo Otimização
original Modelo original (sem otimização com TF-TRT)
tftrt_fp32 Otimização de gráficos
(tamanho do lote=64, grupos de instâncias=1)
tftrt_fp16 Conversão para FP16, além da otimização do gráfico
(tamanho do lote=64, grupos de instâncias=1)
tftrt_int8 Quantização com INT8, além da otimização do gráfico
(tamanho do lote=64, grupos de instâncias=1)
tftrt_int8_bs16_count4 Quantização com INT8, além da otimização do gráfico
(tamanho do lote/16, grupos de instâncias=4)

Implantar servidores de inferência com o Triton

  1. No terminal SSH, instale e configure o pacote de autenticação, que gerencia os clusters do GKE:

    export USE_GKE_GCLOUD_AUTH_PLUGIN=True
    sudo apt-get install google-cloud-sdk-gke-gcloud-auth-plugin
    
  2. Crie um cluster do GKE e um pool de nós de GPU com nós de computação que usam uma 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
    

    A flag --num-nodes especifica 20 instâncias para o cluster do GKE e uma instância para o pool de nós da GPU t4-gpu-pool.

    O pool de nós da GPU consiste em uma única instância n1-standard-8 com uma GPU NVIDIA T4. O número de instâncias de GPU precisa ser igual ou maior que os pods de servidor de inferência de números porque a NVIDIA T4 GPU não pode ser compartilhada por vários pods na mesma instância.

  3. Mostre as informações do cluster:

    gcloud container clusters list
    

    O resultado será assim:

    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. Mostre as informações do pool de nós:

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

    O resultado será assim:

    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. Ative a carga de trabalho 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 carrega o driver GPU da NVIDIA nos nós do pool GPU. Além disso, ele carrega automaticamente o driver quando você adiciona um novo nó ao pool de nós da GPU.

  6. Implante os servidores de inferência no cluster:

    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. Aguarde alguns minutos até que os serviços estejam disponíveis.

  8. Consiga o endereço clusterIP do Triton e armazene-o em uma variável de ambiente:

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

Nesse momento, o servidor de inferência fornece quatro modelos ResNet-50 criados na seção Criar arquivos de modelo com otimizações diferentes. Os clientes podem especificar o modelo a ser usado ao enviar solicitações de inferência.

Implante servidores de monitoramento com o Prometheus e o Grafana

  1. No terminal SSH, implante os servidores do Prometheus no cluster:

    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. Consiga o URL do endpoint do serviço Prometheus.

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

    Anote o URL do endpoint do Prometheus, porque ele será usado para configurar o Grafana posteriormente.

  3. Implante os servidores do Grafana no cluster:

    kubectl create -f grafana-service.yml -n monitoring
    kubectl create -f grafana-deployment.yml -n monitoring
    
  4. Aguarde alguns minutos até que todos os serviços fiquem disponíveis.

  5. Consiga o URL do endpoint do serviço 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}"
    

    Anote o URL do endpoint do Grafana para usar na próxima etapa.

  6. Em um navegador da Web, acesse o URL do Grafana que você anotou na etapa anterior.

  7. Faça login com o ID de usuário e a senha padrão (admin e admin). Quando solicitado, altere a senha padrão.

  8. Clique em Adicionar sua primeira fonte de dados e, na lista Bancos de dados de série temporal, selecione Prometheus.

  9. Na guia Configurações, no campo URL, insira o URL do endpoint do Prometheus que você anotou anteriormente.

  10. Clique em Salvar e testar e retorne à tela inicial.

  11. Adicione uma métrica de monitoramento para nv_gpu_utilization:

    1. Clique em Criar seu primeiro painel e em Adicionar visualização.
    2. Na lista Origem de dados, selecione Prometheus.
    3. Na guia Consulta, no campo Métrica, digite nv_gpu_utilization.

    4. Na seção Panel options, no campo Title, digite GPU Utilization e clique em Apply.

      A página exibe um painel de utilização da GPU.

  12. Adicione uma métrica de monitoramento para nv_gpu_memory_used_bytes:

    1. Clique em Adicionar e selecione Visualização.
    2. Na guia Consulta, no campo Métrica, digite nv_gpu_memory_used_bytes.

    3. Na seção Panel options, no campo Title, digite GPU Memory Used e clique em Save.

  13. Para adicionar o painel, no painel Salvar painel, clique em Salvar.

    Você verá os gráficos de uso da GPU e da memória da GPU usada.

Implantar uma ferramenta de teste de carga

Nesta seção, você implanta a ferramenta de teste de carga do Locust no GKE e gera carga de trabalho para medir o desempenho dos servidores de inferência.

  1. No terminal SSH, crie uma imagem do Docker que contenha as bibliotecas de cliente Triton e faça o upload dela para o 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
    

    O processo de compilação pode levar cerca de 5 minutos. Quando o processo for concluído, um prompt de comando vai aparecer no terminal SSH.

  2. Quando o processo de build estiver concluído, crie uma imagem do Docker para gerar cargas de trabalho de teste e faça upload dela para o 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
    

    Não altere nem substitua YOUR-PROJECT-ID nos comandos.

    Essa imagem é criada a partir da imagem que você criou na etapa anterior.

  3. Implante os arquivos do Locust service_master.yaml e deployment_master.yaml:

    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
    

    O recurso configmap é usado para especificar o modelo de machine learning a que os clientes enviam solicitações de inferência.

  4. Aguarde alguns minutos até que os serviços estejam disponíveis.

  5. Consiga o endereço clusterIP do cliente locust-master e armazene esse endereço em uma variável de ambiente:

    export LOCUST_MASTER_IP=$(kubectl get svc locust-master -n locust \
        -o "jsonpath={.spec['clusterIP']}")
    echo ${LOCUST_MASTER_IP}
    
  6. Implante o 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
    

    Esses comandos implantam 10 pods de cliente do Locust que podem ser usados para gerar cargas de trabalho de teste. Se não for possível gerar solicitações suficientes com o número atual de clientes, altere o número de pods usando o seguinte comando:

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

    Quando não houver capacidade suficiente para que um cluster padrão aumente o número de réplicas, recomendamos que você aumente o número de nós no cluster do GKE.

  7. Copie o URL do console do Locust e abra-o em um navegador da Web:

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

    O console do Locust é aberto e é possível gerar cargas de trabalho de teste a partir dele.

Verificar os pods em execução

Para garantir que os componentes sejam implantados corretamente, verifique se os pods estão em execução.

  1. No terminal SSH, verifique o pod do servidor de inferência:

    kubectl get pods
    

    O resultado será assim:

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

    Se você não receber a saída esperada, verifique se concluiu as etapas em Implantar servidores de inferência usando o Triton.

  2. Verifique os pods do Locust:

    kubectl get pods -n locust
    

    O resultado será assim:

    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
    

    Se você não receber a saída esperada, verifique se concluiu as etapas em Implantar uma ferramenta de teste de carga.

  3. Verifique os pods de monitoramento:

    kubectl get pods -n monitoring
    

    O resultado será assim:

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

    Se você não receber a saída esperada, verifique se concluiu as etapas em Implantar servidores de monitoramento com o Prometheus e o Grafana.

Na próxima parte desta série, você usará esse sistema de servidor de inferência para saber como várias otimizações melhoram o desempenho e como interpretar essas otimizações. Para as próximas etapas, consulte Medir e ajustar o desempenho de um sistema de inferência do TensorFlow.

A seguir