Utiliser TensorFlow Serving avec Kubernetes

Ce didacticiel montre comment utiliser les composants TensorFlow Serving exécutés dans des conteneurs Docker pour servir le modèle TensorFlow ResNet et comment déployer le cluster de service avec Kubernetes.

Pour en savoir plus sur TensorFlow Serving, nous vous recommandons le didacticiel de base TensorFlow Serving et le didacticiel avancé TensorFlow Serving .

Pour en savoir plus sur le modèle TensorFlow ResNet, nous vous recommandons de lire ResNet dans TensorFlow .

Partie 1 : Configuration

Avant de commencer, installez d'abord Docker .

Téléchargez le modèle sauvegardé ResNet

Effacons notre répertoire de modèles locaux au cas où nous en aurions déjà un :

rm -rf /tmp/resnet

Les réseaux résiduels profonds, ou ResNets en abrégé, ont fourni l'idée révolutionnaire des mappages d'identité afin de permettre la formation de réseaux neuronaux convolutifs très profonds. Pour notre exemple, nous allons télécharger un TensorFlow SavedModel de ResNet pour l'ensemble de données ImageNet.

# Download Resnet model from TF Hub
wget https://tfhub.dev/tensorflow/resnet_50/classification/1?tf-hub-format=compressed -o resnet.tar.gz

# Extract SavedModel into a versioned subfolder ‘123’
mkdir -p /tmp/resnet/123
tar xvfz resnet.tar.gz -C /tmp/resnet/123/

Nous pouvons vérifier que nous avons le SavedModel :

$ ls /tmp/resnet/*
saved_model.pb  variables

Partie 2 : Exécuter dans Docker

Valider l’image pour le déploiement

Nous voulons maintenant prendre une image de diffusion et valider toutes les modifications dans une nouvelle image $USER/resnet_serving pour le déploiement de Kubernetes.

Nous exécutons d’abord une image de service en tant que démon :

docker run -d --name serving_base tensorflow/serving

Ensuite, nous copions les données du modèle ResNet dans le dossier modèle du conteneur :

docker cp /tmp/resnet serving_base:/models/resnet

Enfin, nous engageons le conteneur à servir le modèle ResNet :

docker commit --change "ENV MODEL_NAME resnet" serving_base \
  $USER/resnet_serving

Arrêtons maintenant le récipient de base de service

docker kill serving_base
docker rm serving_base

Démarrer le serveur

Démarrons maintenant le conteneur avec le modèle ResNet pour qu'il soit prêt à être servi, en exposant le port gRPC 8500 :

docker run -p 8500:8500 -t $USER/resnet_serving &

Interroger le serveur

Pour le client, nous devrons cloner le dépôt TensorFlow Serving GitHub :

git clone https://github.com/tensorflow/serving
cd serving

Interrogez le serveur avec resnet_client_grpc.py . Le client télécharge une image et l'envoie via gRPC pour être classée dans les catégories ImageNet .

tools/run_in_docker.sh python tensorflow_serving/example/resnet_client_grpc.py

Cela devrait donner un résultat comme :

outputs {
  key: "classes"
  value {
    dtype: DT_INT64
    tensor_shape {
      dim {
        size: 1
      }
    }
    int64_val: 286
  }
}
outputs {
  key: "probabilities"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 1001
      }
    }
    float_val: 2.41628322328e-06
    float_val: 1.90121829746e-06
    float_val: 2.72477100225e-05
    float_val: 4.42638565801e-07
    float_val: 8.98362372936e-07
    float_val: 6.84421956976e-06
    float_val: 1.66555237229e-05
...
    float_val: 1.59407863976e-06
    float_val: 1.2315689446e-06
    float_val: 1.17812135159e-06
    float_val: 1.46365800902e-05
    float_val: 5.81210713335e-07
    float_val: 6.59980651108e-05
    float_val: 0.00129527016543
  }
}
model_spec {
  name: "resnet"
  version {
    value: 123
  }
  signature_name: "serving_default"
}

Ça marche! Le serveur a réussi à classer une image de chat !

Partie 3 : Déployer dans Kubernetes

Dans cette section, nous utilisons l'image de conteneur créée dans la partie 0 pour déployer un cluster de service avec Kubernetes dans Google Cloud Platform .

Connexion au projet GCloud

Ici, nous supposons que vous avez créé et connecté un projet gcloud nommé tensorflow-serving .

gcloud auth login --project tensorflow-serving

Créer un cluster de conteneurs

Nous créons d’abord un cluster Google Kubernetes Engine pour le déploiement du service.

$ gcloud container clusters create resnet-serving-cluster --num-nodes 5

Ce qui devrait produire quelque chose comme :

Creating cluster resnet-serving-cluster...done.
Created [https://container.googleapis.com/v1/projects/tensorflow-serving/zones/us-central1-f/clusters/resnet-serving-cluster].
kubeconfig entry generated for resnet-serving-cluster.
NAME                       ZONE           MASTER_VERSION  MASTER_IP        MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
resnet-serving-cluster  us-central1-f  1.1.8           104.197.163.119  n1-standard-1  1.1.8         5          RUNNING

Définissez le cluster par défaut pour la commande gcloud conteneur et transmettez les informations d'identification du cluster à kubectl .

gcloud config set container/cluster resnet-serving-cluster
gcloud container clusters get-credentials resnet-serving-cluster

ce qui devrait donner lieu à :

Fetching cluster endpoint and auth data.
kubeconfig entry generated for resnet-serving-cluster.

Téléchargez l'image Docker

Transférons maintenant notre image vers Google Container Registry afin de pouvoir l'exécuter sur Google Cloud Platform.

Nous marquons d'abord l'image $USER/resnet_serving en utilisant le format Container Registry et le nom de notre projet,

docker tag $USER/resnet_serving gcr.io/tensorflow-serving/resnet

Ensuite, nous configurons Docker pour utiliser gcloud comme assistant d'identification :

gcloud auth configure-docker

Ensuite, nous transférons l'image vers le registre,

docker push gcr.io/tensorflow-serving/resnet

Créer un déploiement et un service Kubernetes

Le déploiement se compose de 3 répliques du serveur resnet_inference contrôlées par un déploiement Kubernetes . Les réplicas sont exposés en externe par un service Kubernetes avec un équilibreur de charge externe .

Nous les créons en utilisant l'exemple de configuration Kubernetes resnet_k8s.yaml .

kubectl create -f tensorflow_serving/example/resnet_k8s.yaml

Avec sortie :

deployment "resnet-deployment" created
service "resnet-service" created

Pour afficher l'état du déploiement et des pods :

$ kubectl get deployments
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
resnet-deployment    3         3         3            3           5s
$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
resnet-deployment-bbcbc   1/1       Running   0          10s
resnet-deployment-cj6l2   1/1       Running   0          10s
resnet-deployment-t1uep   1/1       Running   0          10s

Pour afficher l'état du service :

$ kubectl get services
NAME                    CLUSTER-IP       EXTERNAL-IP       PORT(S)     AGE
resnet-service       10.239.240.227   104.155.184.157   8500/TCP    1m

Cela peut prendre un certain temps pour que tout soit opérationnel.

$ kubectl describe service resnet-service
Name:           resnet-service
Namespace:      default
Labels:         run=resnet-service
Selector:       run=resnet-service
Type:           LoadBalancer
IP:         10.239.240.227
LoadBalancer Ingress:   104.155.184.157
Port:           <unset> 8500/TCP
NodePort:       <unset> 30334/TCP
Endpoints:      <none>
Session Affinity:   None
Events:
  FirstSeen LastSeen    Count   From            SubobjectPath   Type        Reason      Message
  --------- --------    -----   ----            -------------   --------    ------      -------
  1m        1m      1   {service-controller }           Normal      CreatingLoadBalancer    Creating load balancer
  1m        1m      1   {service-controller }           Normal      CreatedLoadBalancer Created load balancer

L'adresse IP externe du service est répertoriée à côté de LoadBalancer Ingress.

Interroger le modèle

Nous pouvons désormais interroger le service à son adresse externe auprès de notre hôte local.

$ tools/run_in_docker.sh python \
  tensorflow_serving/example/resnet_client_grpc.py \
  --server=104.155.184.157:8500
outputs {
  key: "classes"
  value {
    dtype: DT_INT64
    tensor_shape {
      dim {
        size: 1
      }
    }
    int64_val: 286
  }
}
outputs {
  key: "probabilities"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 1001
      }
    }
    float_val: 2.41628322328e-06
    float_val: 1.90121829746e-06
    float_val: 2.72477100225e-05
    float_val: 4.42638565801e-07
    float_val: 8.98362372936e-07
    float_val: 6.84421956976e-06
    float_val: 1.66555237229e-05
...
    float_val: 1.59407863976e-06
    float_val: 1.2315689446e-06
    float_val: 1.17812135159e-06
    float_val: 1.46365800902e-05
    float_val: 5.81210713335e-07
    float_val: 6.59980651108e-05
    float_val: 0.00129527016543
  }
}
model_spec {
  name: "resnet"
  version {
    value: 1538687457
  }
  signature_name: "serving_default"
}

Vous avez déployé avec succès le modèle ResNet servant de service dans Kubernetes !