Como executar inferência do TensorFlow em grande escala com as GPUs TensorRT 5 e NVIDIA T4

Neste tutorial, você aprenderá a executar uma inferência em grande escala nas GPUs NVIDIA TensorRT 5 e T4. A NVIDIA TensorRT™ é uma plataforma para inferência de aprendizado profundo de alto desempenho, que inclui um otimizador de inferência e um ambiente de execução de aprendizado profundo com baixa latência e alta capacidade para aplicativos desse tipo.

No tutorial, você configurará um cluster multizona para executar uma inferência com um grupo de escalonamento automático baseado no uso da GPU.

Visão geral

Neste tutorial, mostramos o seguinte:

  • Uma arquitetura de referência para implementar um sistema de inferência de machine learning escalonável no Google Cloud que seja adequado a um ambiente para desenvolvedores. Suas necessidades de infraestrutura e segurança variam para que seja possível ajustar de forma adequada as configurações descritas nesse artigo.
  • Um repositório do GitHub que contém scripts a serem usados neste tutorial para instalar o modelo do TensorFlow e outros componentes necessários.
  • Instruções sobre como quantizar o modelo do TensorFlow usando a TensorRT e como implantar scripts e a arquitetura de referência.
  • Instruções sobre como configurar o Cloud Load Balancing.

Ao concluir este tutorial, você terá um modelo quantizado pré-treinado no Cloud Storage e dois grupos de instâncias do Compute Engine em cluster em diferentes regiões fronteadas pelo Cloud Load Balancing para o tráfego da Web. Essa arquitetura é ilustrada no diagrama a seguir.

Arquitetura usada neste tutorial

Objetivos

  • Iniciar um gráfico pré-treinado.
  • Otimizar o modelo com a TensorRT e analisar a velocidade de execução dele com diferentes otimizações.
  • Após a finalização do modelo, criar um cluster com base nas VMs de aprendizado profundo do Compute Engine que já vêm com o TensorFlow, o TensorFlow Serving e a TensorRT 5 pré-instalados.

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

  • Compute Engine
  • Persistent Disk
  • Cloud Storage
  • Cloud Networking
  • GPU NVIDIA T4

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Antes de começar

  1. No Console do Cloud, acesse a página do seletor de projetos.

    Acessar a página do seletor de projetos

  2. Selecione ou crie um projeto do Cloud.

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

  4. Ative as APIs Compute Engine and Cloud Logging.

    Ative as APIs

  5. Verifique se você tem uma cota de GPU suficiente para criar VMs.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Como fazer a limpeza.

Como preparar o ambiente

Nesta seção, você fará as configurações padrão para valores usados no tutorial, como região e zona. O tutorial usa us-central1 como região padrão e us-central1-b como zona padrão.

Você também cria um arquivo com todas as configurações, para poder carregar as variáveis automaticamente se precisar reabrir o Cloud Shell e reinicializar as configurações.

  1. Abra o Cloud Shell:

    ABRIR o Cloud Shell

  2. Defina a região e a zona padrão:

    gcloud compute project-info add-metadata \
        --metadata google-compute-default-region=us-central1,google-compute-default-zone=us-central1-a
    
  3. Reinicie o shell:

    gcloud init --console-only
    
  4. Pressione 1 para as primeiras três perguntas e insira o código do projeto para a última pergunta.

Como otimizar o modelo com o TensorRT

  1. No Cloud Shell, crie uma instância que possa ser usada para a preparação do modelo:

    export IMAGE_FAMILY="tf-latest-cu100"
    export INSTANCE_NAME="model-prep"
    
    gcloud compute instances create $INSTANCE_NAME \
        --image-family=$IMAGE_FAMILY \
        --machine-type=n1-standard-8 \
        --image-project=deeplearning-platform-release \
        --maintenance-policy=TERMINATE \
        --accelerator="type=nvidia-tesla-t4,count=1" \
        --metadata="install-nvidia-driver=True"
    

    Uma GPU é mais que suficiente para comparar diferentes modos de otimização do TensorRT e ter a sensação como uma única GPU pode ser rápida.

  2. Depois de criar a instância de VM, use ssh para estabelecer uma conexão.

  3. Na instância, faça o download do modelo resnetv2 por meio do repositório oficial do TensorFlow para testar a otimização da TensorRT:

    wget -q http://download.tensorflow.org/models/official/resnetv2_imagenet_frozen_graph.pb
    

A TensorRT pode acelerar a inferência, mas a quantização é responsável por outras melhorias. A quantização de modelo linear converte pesos e ativações de pontos flutuantes em números inteiros. Por exemplo, se os pesos iniciais do modelo forem FP32 (32 bits de ponto flutuante), ao reduzir a precisão, é possível usar INT8. Porém, ela traz algumas desvantagens, por exemplo, reduzir a representação de armazenamento também reduz minimamente a precisão do modelo. No entanto, a mudança de FP32 para FP16 é praticamente gratuita.

Como escolher o equilíbrio certo entre velocidade (precisão dos pesos) e acurácia? Há um código que mede a acurácia em relação à velocidade e outras métricas. O teste é limitado a modelos de reconhecimento de imagem, mas é simples implementar um teste personalizado com base nesse código.

  1. No Cloud Shell, faça o download e implemente um teste personalizado:

    git clone https://github.com/tensorflow/models.git
    cd models
    git checkout f0e10716160cd048618ccdd4b6e18336223a172f
    touch research/__init__.py
    touch research/tensorrt/__init__.py
    cp research/tensorrt/labellist.json .
    cp research/tensorrt/image.jpg .
    
  2. Prepare o teste para execução:

    python -m research.tensorrt.tensorrt \
        --frozen_graph=$HOME/resnetv2_imagenet_frozen_graph.pb \
        --image_file=$HOME/models/image.jpg \
        --native --fp32 --fp16 --int8 \
        --output_dir=$HOME
    

    O teste requer um gráfico congelado, ou seja, o modelo resnetv2 do qual você fez download anteriormente, e argumentos para os diferentes modos de quantização a serem testados. Esse comando leva algum tempo para ser processado.

    A saída resultante da execução é uma comparação do resultado da inferência para uma versão diferente do gráfico:

    comparação do resultado da inferência para uma versão diferente do gráfico

    Os resultados do FP32 e FP16 são idênticos e mostram a mesma precisão, o que significa que, se a TensorRT funcionar bem para você, então poderá usar o FP16. Já o INT8 mostra resultados um pouco menos precisos.

  3. Exiba os números de precisão:

    cat $HOME/log.txt
    

    Neste comando, a seguinte saída é produzida:

    inferência em registro de grande escala

A TensorRT 5 mostra os seguintes resultados, todos comparados aos nativos:

  • Para FP32, a capacidade melhorou cerca de 34%, de 319,1 fps a 428,2 fps.
  • Para FP16, a capacidade melhorou 207%, de 319,1 fps a 979,6 fps.
  • Para INT8, a capacidade melhorou cerca de 376%, de 319,1 fps a 1519,5 fps.

O que podemos aprender com esses resultados:

  • Mudar de nativo para TensorRT impacta a incerteza. No entanto, se você preferir um custo mais baixo, mude para o FP16.
  • O INT8 é muito rápido, mas o preço é bem mais alto.

A próxima seção usa o modelo INT8.

Como converter um modelo personalizado em TensorRT

Para converter um modelo em um gráfico da TensorRT, você precisa de um SavedModel.

  1. No Cloud Shell, faça o download do seguinte modelo de salvamento pré-treinado:

    wget http://download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NCHW.tar.gz
    tar -xzvf resnet_v2_fp32_savedmodel_NCHW.tar.gz
    
  2. Crie um script Python que converta o modelo congelado em um gráfico da TensorRT:

    cat <<EOF > convert_to_rt.py
    import tensorflow.contrib.tensorrt as trt
    import argparse
    
    parser = argparse.ArgumentParser(description="Converts TF SavedModel to the TensorRT enabled graph.")
    
    parser.add_argument("--input_model_dir", required=True)
    parser.add_argument("--output_model_dir", required=True)
    parser.add_argument("--batch_size", type=int, required=True)
    parser.add_argument("--precision_mode", choices=["FP32", "FP16", "INT8"], required=True)
    
    args = parser.parse_args()
    
    trt.create_inference_graph(
        None, None, max_batch_size=args.batch_size,
        input_saved_model_dir=args.input_model_dir,
        output_saved_model_dir=args.output_model_dir,
        precision_mode=args.precision_mode)
    EOF
    
  3. Converta o modelo em um gráfico da TensorRT:

    python ./convert_to_rt.py \
        --input_model_dir=$HOME/resnet_v2_fp32_savedmodel_NCHW/1538687196 \
        --output_model_dir=$HOME/resnet_v2_int8_NCHW/00001 \
        --batch_size=128 \
        --precision_mode="INT8"
    

    Agora há um modelo INT8 na pasta $HOME/resnet_v2_int8_NCHW/00001.

  4. Execute uma inferência para garantir que tudo está funcionando:

    tensorflow_model_server --model_base_path=$HOME/resnet_v2_int8_NCHW/ --rest_api_port=8888
    
  5. Para verificar o andamento do processo, envie a seguinte entrada de amostra:

    curl -X POST localhost:8888/v1/models/default:predict -d '{"instances": [[[[1,1,1]]]]}'
    
  6. Se forem exibidos resultados desse comando curl, saia da execução de inferência pressionando Ctrl + C.

  7. Para usar o modelo otimizado do seu cluster, faça upload do modelo no Cloud Storage substituindo [GCS_PATH] pelo nome do bucket do Cloud Storage:

    tar -zcvf model.tar.gz ./resnet_v2_int8_NCHW/
    export GCS_PATH=[GCS_PATH]
    gsutil cp model.tar.gz $GCS_PATH
    

    Você não precisará repetir esse processo na próxima vez que usar o modelo. Em vez disso, poderá usar o gráfico congelado INT8 que está no bucket do Cloud Storage:

    gs://solutions-public-assets/tensorrt-t4-gpu/model.tar.gz
    

Como configurar um cluster

Agora que você tem um modelo no Cloud Storage, crie um cluster. O primeiro passo é criar um modelo de VM. O cluster usa esse modelo para criar novas instâncias.

  1. No Cloud Shell, faça o download do código necessário para configurar um cluster:

    git clone https://github.com/GoogleCloudPlatform/tensorflow-inference-tensorrt5-t4-gpu.git
    
  2. Crie o modelo de VM substituindo [PROJECT_NAME] pelo nome do seu projeto:

    export PROJECT_NAME=[PROJECT_NAME]
    export INSTANCE_TEMPLATE_NAME="tf-inference-template"
    export IMAGE_FAMILY="tf-latest-cu100"
    
    gcloud beta compute --project=$PROJECT_NAME instance-templates create $INSTANCE_TEMPLATE_NAME \
        --machine-type=n1-standard-16 \
        --maintenance-policy=TERMINATE \
        --accelerator=type=nvidia-tesla-t4,count=4 \
        --min-cpu-platform=Intel\ Skylake \
        --tags=http-server,https-server \
        --image-family=$IMAGE_FAMILY \
        --image-project=deeplearning-platform-release \
        --boot-disk-size=100GB \
        --boot-disk-type=pd-ssd \
        --boot-disk-device-name=$INSTANCE_TEMPLATE_NAME \
        --metadata startup-script-url=gs://solutions-public-assets/tensorrt-t4-gpu/start_agent_and_inf_server.sh
    

    O parâmetro metadata especifica um script de inicialização instalado em cada instância criada pelo modelo de VM. Esse script executa os seguintes procedimentos quando a instância de VM é iniciada:

    • Instala os drivers da NVIDIA.
    • Instala um agente de monitoramento para monitorar o uso da GPU.
    • Faz o download do modelo.
    • Inicia o serviço de inferência.

    Quando o modelo estiver pronto, você poderá criar o grupo de instâncias gerenciadas. O grupo não será de escalonamento, e não haverá uma verificação de integridade. Você criará um grupo que só garante duas instâncias em execução em zonas específicas.

  3. Crie o grupo de instâncias gerenciadas:

    gcloud compute instance-groups managed create $INSTANCE_GROUP_NAME \
        --template $INSTANCE_TEMPLATE_NAME \
        --base-instance-name deeplearning-instances \
        --size 2 \
        --zones us-central1-a,us-central1-b
    

    O valor de INSTANCE_TEMPLATE_NAME é o nome da instância que você definiu anteriormente. Escolha as zonas com base nas suas cotas e na disponibilidade das GPUs. Lembre-se de que nem todas as GPUs estão disponíveis em todas as zonas.

    O processo de criação do grupo leva alguns minutos.

  4. Acompanhe o progresso executando o seguinte comando:

    gcloud compute instance-groups managed list-instances $INSTANCE_GROUP_NAME --region us-central1
    

    A saída terá a seguinte aparência:

    enquanto o grupo está sendo criado

    Quando o processo de criação for concluído, você verá o seguinte:

    depois de criar o grupo

  5. Abra a página de monitoramento no Console do Cloud.

    Acessar a página "Monitoramento"

  6. No canto superior esquerdo, verifique se você está no espaço de trabalho do projeto correto. Caso seja seu primeiro acesso nesta página, crie um novo espaço de trabalho.

  7. Na página Metrics Explorer, para Tipo de recurso, selecione GCE VM Instance e para Métricas, selecione custom/gpu_utilization:

    Página

    Se os dados estiverem sendo recebidos, você verá algo como:

    Gráfico de métricas mostrando uso zero

  8. No Cloud Shell, ative o escalonamento automático para seu grupo:

    gcloud compute instance-groups managed set-autoscaling $INSTANCE_GROUP_NAME \
        --custom-metric-utilization metric=custom.googleapis.com/gpu_utilization,utilization-target-type=GAUGE,utilization-target=85 \
        --max-num-replicas 4 \
        --cool-down-period 360 \
        --region us-central1
    

    O importante aqui é o caminho de utilização, custom.googleapis.com/gpu_utilization, que é o caminho completo para sua métrica. Além disso, como você definiu um nível de destino como 85, sempre que a utilização da GPU atingir esse valor, uma nova instância será criada no seu grupo.

Como testar o escalonamento automático

Para testar o escalonamento automático que você configurou na seção anterior, faça o seguinte:

  • Use o SSH para se conectar a uma das instâncias de GPU de aprendizado profundo.
  • Carregue todas as GPUs até 100%.
  • Observe como seu grupo de escalonamento automático aumenta ao criar mais uma instância.

  1. Conecte-se à instância por meio de ssh.
  2. Carregue sua GPU até 100% por 600 segundos:

    git clone https://github.com/GoogleCloudPlatform/tensorflow-inference-tensorrt5-t4-gpu.git
    cd tensorflow-inference-tensorrt5-t4-gpu
    git submodule update --init --recursive
    cd third_party/gpu-burn
    make
    ./gpu_burn 600 > /dev/null &
    

    No Console do Cloud, observe a atividade na página Metrics Explorer:

    pico de atividade na página

  3. Crie a segunda instância.

  4. Vá para a página GCE Compute > Grupos de instâncias > Monitoramento e observe a atividade:

    pico de atividade mais alto no monitoramento

    Nesse momento, o escalonador automático está tentando gerar o maior número de instâncias possível para reduzir a carga, mas o processo não é bem-sucedido. Veja o que acontece:

    gerando muitas instâncias

  5. Interrompa a criação de instâncias e observe como a atividade diminui.

Isto é o que você tem no momento:

  • Um modelo treinado otimizado com TensorRT 5 (INT8)
  • Um grupo gerenciado de instâncias de aprendizado profundo
  • Escalonamento automático baseado no uso da GPU

Como criar um balanceador de carga

A etapa final é criar um balanceador de carga para as instâncias.

  1. No Cloud Shell, crie verificações de integridade para determinar se um determinado host do seu back-end pode veicular o tráfego:

    gcloud compute health-checks create http $HEALTH_CHECK_NAME \
        --request-path /v1/models/default \
        --port 8888
    
  2. Configure as portas nomeadas do grupo de instâncias de modo que o balanceador de carga possa encaminhar as solicitações de inferência por meio da porta 80 para o serviço de inferência na porta 8888:

    gcloud compute instance-groups set-named-ports $INSTANCE_GROUP_NAME \
        --named-ports http:8888 \
        --region us-central1
    
  3. Crie um serviço de back-end:

    export WEB_BACKED_SERVICE_NAME="tensorflow-backend"
    
    gcloud compute backend-services create $WEB_BACKED_SERVICE_NAME \
        --protocol HTTP \
        --health-checks $HEALTH_CHECK_NAME \
        --global
    

    Um serviço de back-end é um grupo de instâncias com verificação de integridade.

  4. Adicione seu grupo de instâncias ao novo serviço de back-end:

    gcloud compute backend-services add-backend $WEB_BACKED_SERVICE_NAME \
        --balancing-mode UTILIZATION \
        --max-utilization 0.8 \
        --capacity-scaler 1 \
        --instance-group $INSTANCE_GROUP_NAME \
        --instance-group-region us-central1 \
        --global
    
  5. Informe ao balanceador de carga qual URL precisa ser encaminhado para o serviço de back-end:

    export WEB_MAP_NAME="map-all"
    
    gcloud compute url-maps create $WEB_MAP_NAME \
        --default-service $WEB_BACKED_SERVICE_NAME
    
  6. Crie o balanceador de carga:

    export LB_NAME="tf-lb"
    
    gcloud compute target-http-proxies create $LB_NAME \
        --url-map $WEB_MAP_NAME
    
  7. Crie um endereço IP externo para seu balanceador de carga:

    export IP4_NAME="lb-ip4"
    
    gcloud compute addresses create $IP4_NAME \
        --ip-version=IPV4 \
        --global
    
  8. Verifique se o endereço IP foi alocado:

    gcloud compute addresses list
    
  9. Revise a regra de encaminhamento que o Google Cloud usa para encaminhar todas as solicitações do IP público para o balanceador de carga:

    export IP=$(gcloud compute addresses list | grep ${IP4_NAME} | awk '{print $2}')
    export FORWARDING_RULE="lb-fwd-rule"
    
    gcloud compute forwarding-rules create $FORWARDING_RULE \
        --address $IP \
        --global \
        --target-http-proxy $LB_NAME \
        --ports 80
    

    Depois de criar as regras de encaminhamento globais, pode levar alguns minutos para a configuração ser propagada.

  10. Para se conectar a instâncias externas, ative o firewall no projeto:

    gcloud compute firewall-rules create www-firewall-80 \
        --target-tags http-server --allow tcp:80
    
    gcloud compute firewall-rules create www-firewall-8888 \
        --target-tags http-server --allow tcp:8888
    
  11. Converta a imagem para um formato que possa ser enviado ao servidor:

    cat <<EOF > load_and_convert_image.py
    from PIL import Image
    import numpy as np
    import json
    import codecs
    
    img = Image.open("image.jpg").resize((240, 240))
    img_array=np.array(img)
    result = {
        "instances":[img_array.tolist()]
    }
    file_path="/tmp/out.json"
    print(json.dump(result, codecs.open(file_path, 'w', encoding='utf-8'), separators=(',', ':'), sort_keys=True, indent=4))
    EOF
    
  12. Execute uma inferência:

    wget https://pixnio.com/free-images/2017/10/31/2017-10-31-10-43-58-1032x825.jpg -O image.jpg
    python load_and_convert_image.py
    curl -X POST $IP/v1/models/default:predict -d @/tmp/out.json
    

    Se a inferência funcionar corretamente, o resultado será semelhante a este:

    Resultado bem-sucedido da execução de inferência

Como fazer a limpeza

Depois de concluir este tutorial, limpe os recursos criados no Google Cloud para que eles não ocupem a cota e você não seja cobrado por eles no futuro. Veja como excluir e desativar esses recursos nas seções a seguir.

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar a página "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir