Exécuter une inférence TensorFlow à grande échelle avec TensorRT 5 et des GPU NVIDIA T4

Ce tutoriel explique comment lancer une opération d'inférence à grande échelle en utilisant NVIDIA TensorRT 5 et des GPU T4. NVIDIA TensorRT™ est une plate-forme d'inférence haute performance pour le deep learning. Cette plate-forme inclut un optimiseur de processus d'inférence et un environnement d'exécution qui permettent de bénéficier d'un débit élevé et d'une faible latence pour les applications d'inférence liées au deep learning.

Dans le tutoriel, vous allez définir un cluster multizone afin d'exécuter une charge de travail d'inférence avec un groupe d'instances pour lesquelles l'autoscaling est activé. Le nombre d'instances utilisées dépendra de l'utilisation GPU.

Présentation

Ce tutoriel fournit les éléments suivants :

  • Une architecture de référence pour la mise en œuvre d'un système d'inférence pour les opérations de machine learning sur Google Cloud, à la fois évolutif et adapté à un environnement de développement. Vos besoins en termes d'infrastructure et de sécurité pourraient ne pas correspondre à ce qui a été retenu pour ce tutoriel. Vous pouvez bien évidemment ajuster les configurations décrites dans ce document en conséquence.
  • Un dépôt GitHub contenant les scripts que vous exécuterez dans le cadre de ce tutoriel pour installer le modèle TensorFlow et les autres composants requis.
  • Des instructions pour la quantification du modèle TensorFlow à l'aide de TensorRT, pour le déploiement des scripts et pour le déploiement de l'architecture de référence.
  • Des instructions relatives à la configuration de Cloud Load Balancing.

Lorsque vous aurez terminé ce tutoriel, vous disposerez d'un modèle pré-entraîné et quantifié qui sera hébergé dans Cloud Storage, de deux groupes d'instances Compute Engine déployés dans des zones différentes, ainsi que d'une solution Cloud Load Balancing frontale permettant d'équilibrer le trafic Web destiné aux instances. Cette architecture est illustrée dans le schéma suivant.

Architecture utilisée dans ce tutoriel

Objectifs

  • Vous allez commencer avec un graphe pré-entraîné.
  • Dans un second temps, vous optimiserez le modèle avec TensorRT et pourrez constater qu'il est possible de le rendre bien plus rapide en procédant à différentes optimisations.
  • Une fois le modèle finalisé, vous créerez un cluster composé de VM Compute Engine dédiées au deep learning, sur lesquelles TensorFlow, TensorFlow Serving et TensorRT 5 sont préinstallés.

Coûts

Ce tutoriel utilise les composants facturables suivants de Google Cloud :

  • Compute Engine
  • Persistent Disk
  • Cloud Storage
  • Mise en réseau
  • GPU NVIDIA T4

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Dans Cloud Console, accédez à la page de sélection du projet.

    Accéder à la page de sélection du projet

  2. Sélectionnez ou créez un projet Cloud.

  3. Vérifiez que la facturation est activée pour votre projet Google Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Activer les API Compute Engine and Cloud Logging.

    Activer les API

  5. Assurez-vous que vous disposez d'un quota de GPU suffisant pour créer des machines virtuelles.

Une fois que vous avez terminé ce tutoriel, vous pouvez éviter de continuer à payer des frais en supprimant les ressources que vous avez créées. Consultez la page Effectuer un nettoyage pour en savoir plus.

Préparer l'environnement

Dans cette section, vous définissez des paramètres par défaut pour les valeurs utilisées tout au long du tutoriel, telles que la région et la zone. Dans ce tutoriel, la région par défaut est us-central1, et la zone par défaut est us-central1-b.

Vous devez également créer un fichier comprenant tous les paramètres, de façon à pouvoir charger les variables automatiquement si vous devez rouvrir Cloud Shell et réinitialiser les paramètres.

  1. Ouvrez Cloud Shell.

    OUVRIR Cloud Shell

  2. Définissez la région et la zone par défaut :

    gcloud compute project-info add-metadata \
        --metadata google-compute-default-region=us-central1,google-compute-default-zone=us-central1-a
    
  3. Réinitialisez l'interface système :

    gcloud init --console-only
    
  4. Appuyez sur 1 pour les trois premières questions, puis saisissez l'ID du projet pour la dernière question.

Optimiser le modèle avec TensorRT

  1. Dans Cloud Shell, créez l'instance que vous allez utiliser pour la préparation du modèle :

    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"
    

    Le fait d'utiliser un seul GPU suffit amplement pour comparer différents modes d'optimisation TensorRT. En outre, cela permet de se faire une idée de la vitesse que peut atteindre un GPU unique.

  2. Une fois l'instance de VM créée, utilisez ssh pour vous connecter à la machine virtuelle.

  3. Dans l'instance, téléchargez le modèle resnetv2 depuis le dépôt officiel TensorFlow. Ce modèle va vous permettre de tester l'optimisation TensorRT :

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

TensorRT peut accroître la vitesse des charges de travail d'inférence, mais la quantification permet d'aller encore plus loin dans l'accélération du processus. La quantification de modèle linéaire consiste à convertir en nombres entiers les pondérations et activations exprimées sous forme de valeurs à virgule flottante. Par exemple, si les pondérations initiales du modèle sont de type FP32 (32 bits à virgule flottante), vous pouvez utiliser le type INT8 en réduisant la précision. Mais la quantification n'est pas sans incidence : en réduisant la représentation en mémoire, vous risquez de réduire fortement la justesse du modèle. En revanche, le passage du type FP32 au type FP16 n'a quasiment aucune incidence.

Comment choisir le bon compromis entre vitesse (précision des pondérations) et justesse ? Il existe un code qui fait cela. Il peut évaluer le degré de justesse atteignable en prenant en considération la vitesse ou d'autres métriques. Pour l'instant, ce code ne permet de tester que les modèles de reconnaissance d'image, mais il n'est pas très difficile de mettre en œuvre un test personnalisé en se basant sur ce code.

  1. Dans Cloud Shell, téléchargez et mettez en œuvre un test personnalisé :

    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. Préparez le test en vue de son exécution :

    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
    

    Le test nécessite un graphe figé (le modèle resnetv2 que vous avez téléchargé précédemment) et des arguments pour les différents modes de quantification que vous souhaitez tester. Sachez que cette opération peut prendre un certain temps.

    Le résultat renvoyé à la suite de l'exécution de la commande est une comparaison des résultats du processus d'inférence pour une version différente du graphe :

    comparer le résultat de l'inférence pour une version différente du graphe

    Les résultats, qui sont identiques pour FP32 et FP16, offrent le même degré de justesse. Si vous considérez que le résultat du test TensorRT est concluant, vous pouvez commencer à utiliser FP16 immédiatement. On peut également constater que les résultats pour INT8 sont légèrement moins précis.

  3. Affichez les valeurs de justesse :

    cat $HOME/log.txt
    

    Cette commande produit le résultat suivant :

    fichier journal de l'inférence à grande échelle

TensorRT 5 indique les résultats suivants, tous comparés aux valeurs natives :

  • Pour FP32, le débit a été amélioré d'environ 34 %. Il est passé de 319,1 FPS à 428,2 FPS.
  • Pour FP16, le débit a été amélioré d'environ 207 %. Il est passé de 319,1 FPS à 979,6 FPS.
  • Pour INT8, le débit a été amélioré d'environ 376 %. Il est passé de 319,1 FPS à 1 519,5 FPS.

Ce que vous pouvez apprendre de ces résultats :

  • Le passage du modèle natif au modèle optimisé par TensorRT a une incidence non négligeable sur l'incertitude. Toutefois, si vous acceptez une légère perte de précision, vous pouvez passer directement au type FP16.
  • Le type INT8 est très rapide, mais le prix à payer en termes de perte de précision est sensiblement plus élevé.

La section suivante utilise le modèle INT8.

Convertir un modèle personnalisé en graphe TensorRT

Pour convertir un modèle en graphe TensorRT, vous avez besoin d'un SavedModel.

  1. Dans Cloud Shell, téléchargez le SavedModel pré-entraîné suivant :

    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. Créez un script Python permettant de convertir le modèle figé en graphe 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. Convertissez le modèle en graphe 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"
    

    Désormais, le dossier $HOME/resnet_v2_int8_NCHW/00001 contient un modèle INT8.

  4. Lancez un test d'inférence pour vous assurer que tout fonctionne correctement :

    tensorflow_model_server --model_base_path=$HOME/resnet_v2_int8_NCHW/ --rest_api_port=8888
    
  5. Pour vérifier que tout fonctionne, envoyez l'exemple d'entrée suivant :

    curl -X POST localhost:8888/v1/models/default:predict -d '{"instances": [[[[1,1,1]]]]}'
    
  6. Si vous voyez les résultats de cette commande curl s'afficher, arrêtez le test d'inférence en appuyant sur Ctrl+C.

  7. Pour utiliser le modèle optimisé depuis votre cluster, importez le modèle dans Cloud Storage. Pour cela, vous devez remplacer [GCS_PATH] par le nom de votre bucket Cloud Storage :

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

    La prochaine fois que vous souhaiterez utiliser ce modèle, vous n'aurez pas à répéter ce processus. Au lieu de cela, vous pourrez directement utiliser le graphe figé INT8 qui se trouve dans votre bucket Cloud Storage :

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

Configurer un cluster

Maintenant que vous avez importé un modèle dans Cloud Storage, vous pouvez créer un cluster. La première étape consiste à créer un modèle de VM. Le cluster utilisera ensuite ce modèle de VM pour créer des instances de machine virtuelle.

  1. Dans Cloud Shell, téléchargez le code dont vous avez besoin pour configurer le cluster :

    git clone https://github.com/GoogleCloudPlatform/tensorflow-inference-tensorrt5-t4-gpu.git
    
  2. Créez le modèle de VM en remplaçant [PROJECT_NAME] par le nom de votre projet :

    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
    

    Le paramètre metadata fait référence à un script de démarrage qui sera installé sur chaque instance créée à partir du modèle de VM. Ce script effectue les procédures suivantes lors du démarrage de l'instance de VM :

    • Installation des pilotes NVIDIA
    • Installation d'un agent chargé de surveiller l'utilisation du GPU
    • Téléchargement du modèle
    • Démarrage du service d'inférence

    Lorsque le modèle est prêt, vous pouvez créer le groupe d'instances géré. Ce dernier ne sera pas utilisé en tant que groupe de scaling et aucune vérification de l'état ne sera effectuée sur les instances de VM. La création de ce groupe ne vous offre qu'une seule garantie : celle d'avoir deux instances en cours d'exécution dans des zones spécifiques.

  3. Créez le groupe d'instances géré :

    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
    

    La valeur INSTANCE_TEMPLATE_NAME correspond au nom de l'instance que vous avez configurée précédemment. Choisissez des zones en fonction de la disponibilité des GPU (tous les GPU ne sont pas disponibles dans toutes les zones) et en fonction de vos quotas.

    La création du groupe prend un certain temps.

  4. Exécutez la commande suivante pour surveiller la progression :

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

    Le résultat ressemble à ceci :

    lors de la création du groupe

    Lorsque la création est terminée, vous obtenez quelque chose de semblable à ceci :

    après la création du groupe

  5. Ouvrez la page "Surveillance" dans Cloud Console.

    Accéder à la page "Surveillance"

  6. Assurez-vous que vous êtes dans l'espace de travail associé à votre projet (cette information se trouve dans le coin supérieur gauche de la fenêtre). Si vous accédez à cette page pour la première fois, vous devez créer un espace de travail.

  7. Sur la page Metrics Explorer (Explorateur de métriques), pour la valeur Resource type (Type de ressource), sélectionnez GCE VM Instance. Pour la valeur Metric (Métrique), sélectionnez custom/gpu_utilization :

    Page Metrics Explorer (Explorateur de métriques)

    Si des données arrivent, vous voyez quelque chose de semblable à ceci :

    Graphique des métriques indiquant une utilisation nulle

  8. Dans Cloud Shell, activez l'autoscaling pour votre groupe :

    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
    

    La partie importante ici est le chemin d'utilisation (custom.googleapis.com/gpu_utilization), qui représente le chemin d'accès complet de votre métrique. Notez également que le niveau d'utilisation cible (utilization-target) est défini sur 85. Cela implique qu'une instance sera créée dans votre groupe à chaque fois que l'utilisation GPU atteint 85 %.

Tester l'autoscaling

Pour tester l'autoscaling, que vous avez configuré dans la section précédente, vous devez procéder comme suit :

  • Utilisez SSH pour vous connecter à l'une des instances de deep learning basées sur des GPU.
  • Faites grimper le taux d'utilisation de tous les GPU à 100 %.
  • Observez comment votre groupe d'autoscaling se met à l'échelle automatiquement en créant une instance supplémentaire.

  1. Connectez-vous à l'instance via ssh.
  2. Faites grimper le taux d'utilisation du GPU à 100 % et maintenez-le à ce niveau pendant 600 secondes :

    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 &
    

    Dans Cloud Console, vous pouvez accéder à la page Metrics Explorer (Explorateur de métriques) pour constater et suivre l'activité de l'instance :

    pic d'activité dans la page Metrics Explorer

  3. Créez la deuxième instance.

  4. Accédez à la page GCE Compute (Calcul GCE) > Instance Groups (Groupes d'instances) > Monitoring (Surveillance) et observez l'activité :

    pic d'activité plus élevé dans la page Monitoring

    À ce stade, l'autoscaler tente de démarrer autant d'instances que possible afin de réduire la charge (sans toutefois y parvenir). Et c'est bien ce que l'on constate à l'écran :

    démarrer un grand nombre d'instances

  5. Arrêtez le démarrage de nouvelles instances et observez la réduction de l'activité.

Observons les éléments dont vous disposez désormais :

  • Un modèle entraîné, optimisé à l'aide de TensorRT 5 (INT8)
  • Un groupe d'instances de deep learning géré
  • Un autoscaling basé sur l'utilisation GPU

Créer un équilibreur de charge

La dernière étape consiste à créer un équilibreur de charge devant les instances.

  1. Dans Cloud Shell, créez des points de "vérification de l'état" afin de déterminer si un hôte particulier de votre backend peut diffuser le trafic :

    gcloud compute health-checks create http $HEALTH_CHECK_NAME \
        --request-path /v1/models/default \
        --port 8888
    
  2. Configurez les ports nommés du groupe d'instances pour que l'équilibreur de charge puisse transférer les requêtes d'inférence arrivant sur le port 80 au service d'inférence, lequel est desservi via le port 8888 :

    gcloud compute instance-groups set-named-ports $INSTANCE_GROUP_NAME \
        --named-ports http:8888 \
        --region us-central1
    
  3. Créez un service de backend :

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

    Comme vous pouvez le remarquer, un service de backend est un groupe d'instances avec vérification de l'état (Health Check).

  4. Ajoutez votre groupe d'instances au service de backend nouvellement créé :

    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. Indiquez à l'équilibreur de charge quelle est l'URL à transférer au service de backend :

    export WEB_MAP_NAME="map-all"
    
    gcloud compute url-maps create $WEB_MAP_NAME \
        --default-service $WEB_BACKED_SERVICE_NAME
    
  6. Créez l'équilibreur de charge :

    export LB_NAME="tf-lb"
    
    gcloud compute target-http-proxies create $LB_NAME \
        --url-map $WEB_MAP_NAME
    
  7. Créez une adresse IP externe pour votre équilibreur de charge :

    export IP4_NAME="lb-ip4"
    
    gcloud compute addresses create $IP4_NAME \
        --ip-version=IPV4 \
        --global
    
  8. Contrôlez que l'adresse IP a été attribuée :

    gcloud compute addresses list
    
  9. Vérifiez la règle de transfert utilisée par Google Cloud pour rediriger toutes les requêtes arrivant au niveau de l'adresse IP publique vers l'équilibreur de charge :

    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
    

    Une fois les règles de transfert globales créées, la propagation de votre configuration peut prendre plusieurs minutes.

  10. Pour vous connecter à des instances externes, activez le pare-feu pour le projet :

    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. Convertissez l'image dans un format d'entrée accepté par le serveur :

    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. Lancez une opération d'inférence :

    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
    

    Si l'inférence fonctionne correctement, le résultat renvoyé doit ressembler à ceci :

    Résultat de l'exécution réussie d'une inférence

Effectuer un nettoyage

Une fois ce tutoriel terminé, vous pouvez nettoyer les ressources que vous avez créées dans Google Cloud afin qu'elles ne soient pas prises en compte et qu'elles ne vous soient plus facturées. Dans les sections suivantes, nous allons voir comment supprimer ou désactiver ces ressources.

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer .
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Étapes suivantes