Création d'un serveur modèle TensorFlow standard

Ce didacticiel vous montre comment utiliser les composants TensorFlow Serving pour créer le TensorFlow ModelServer standard qui découvre et diffuse dynamiquement de nouvelles versions d'un modèle TensorFlow entraîné. Si vous souhaitez simplement utiliser le serveur standard pour diffuser vos modèles, consultez le didacticiel de base sur TensorFlow Serving .

Ce didacticiel utilise le modèle simple de régression Softmax introduit dans le didacticiel TensorFlow pour la classification des images manuscrites (données MNIST). Si vous ne savez pas ce qu'est TensorFlow ou MNIST, consultez le didacticiel MNIST For ML Beginners .

Le code de ce didacticiel se compose de deux parties :

  • Un fichier Python mnist_saved_model.py qui entraîne et exporte plusieurs versions du modèle.

  • Un fichier C++ main.cc qui est le TensorFlow ModelServer standard qui découvre les nouveaux modèles exportés et exécute un service gRPC pour les servir.

Ce didacticiel passe en revue les tâches suivantes :

  1. Entraînez et exportez un modèle TensorFlow.
  2. Gérez la gestion des versions du modèle avec TensorFlow Serving ServerCore .
  3. Configurez le traitement par lots à l’aide de SavedModelBundleSourceAdapterConfig .
  4. Servir la requête avec TensorFlow Serving ServerCore .
  5. Exécutez et testez le service.

Avant de commencer, installez d'abord Docker

Entraîner et exporter le modèle TensorFlow

Tout d'abord, si vous ne l'avez pas encore fait, clonez ce référentiel sur votre machine locale :

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

Effacez le répertoire d'exportation s'il existe déjà :

rm -rf /tmp/models

Entraîner (avec 100 itérations) et exporter la première version du modèle :

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=100 --model_version=1 /tmp/mnist

Entraîner (avec 2000 itérations) et exporter la deuxième version du modèle :

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=2000 --model_version=2 /tmp/mnist

Comme vous pouvez le voir dans mnist_saved_model.py , la formation et l'exportation se font de la même manière que dans le didacticiel de base de TensorFlow Serving . À des fins de démonstration, vous réduisez intentionnellement les itérations d'entraînement pour la première exécution et les exportez en tant que v1, tout en les entraînant normalement pour la deuxième exécution et en les exportant en tant que v2 vers le même répertoire parent - comme nous nous attendons à ce que ce dernier le fasse. une meilleure précision de classification grâce à une formation plus intensive. Vous devriez voir les données d'entraînement pour chaque exécution d'entraînement dans votre répertoire /tmp/mnist :

$ ls /tmp/mnist
1  2

ServeurCore

Imaginez maintenant que les versions v1 et v2 du modèle soient générées dynamiquement au moment de l'exécution, au fur et à mesure que de nouveaux algorithmes sont expérimentés ou que le modèle est entraîné avec un nouvel ensemble de données. Dans un environnement de production, vous souhaiterez peut-être créer un serveur capable de prendre en charge un déploiement progressif, dans lequel la v2 peut être découverte, chargée, expérimentée, surveillée ou annulée tout en servant la v1. Alternativement, vous souhaiterez peut-être supprimer la v1 avant d’afficher la v2. TensorFlow Serving prend en charge les deux options : l'une permet de maintenir la disponibilité pendant la transition, l'autre permet de minimiser l'utilisation des ressources (par exemple, la RAM).

TensorFlow Serving Manager fait exactement cela. Il gère le cycle de vie complet des modèles TensorFlow, y compris leur chargement, leur diffusion et leur déchargement, ainsi que les transitions de version. Dans ce didacticiel, vous allez construire votre serveur au-dessus d'un TensorFlow Serving ServerCore , qui encapsule en interne un AspiredVersionsManager .

int main(int argc, char** argv) {
  ...

  ServerCore::Options options;
  options.model_server_config = model_server_config;
  options.servable_state_monitor_creator = &CreateServableStateMonitor;
  options.custom_model_config_loader = &LoadCustomModelConfig;

  ::google::protobuf::Any source_adapter_config;
  SavedModelBundleSourceAdapterConfig
      saved_model_bundle_source_adapter_config;
  source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
  (*(*options.platform_config_map.mutable_platform_configs())
      [kTensorFlowModelPlatform].mutable_source_adapter_config()) =
      source_adapter_config;

  std::unique_ptr<ServerCore> core;
  TF_CHECK_OK(ServerCore::Create(options, &core));
  RunServer(port, std::move(core));

  return 0;
}

ServerCore::Create() prend un paramètre ServerCore::Options. Voici quelques options couramment utilisées :

  • ModelServerConfig qui spécifie les modèles à charger. Les modèles sont déclarés soit via model_config_list , qui déclare une liste statique de modèles, soit via custom_model_config , qui définit une manière personnalisée de déclarer une liste de modèles pouvant être mis à jour au moment de l'exécution.
  • PlatformConfigMap qui mappe le nom de la plate-forme (comme tensorflow ) au PlatformConfig , qui est utilisé pour créer le SourceAdapter . SourceAdapter adapte StoragePath (le chemin où une version de modèle est découverte) au model Loader (charge la version du modèle à partir du chemin de stockage et fournit des interfaces de transition d'état au Manager ). Si PlatformConfig contient SavedModelBundleSourceAdapterConfig , un SavedModelBundleSourceAdapter sera créé, ce que nous expliquerons plus tard.

SavedModelBundle est un composant clé de TensorFlow Serving. Il représente un modèle TensorFlow chargé à partir d'un chemin donné et fournit la même Session::Run que TensorFlow pour exécuter l'inférence. SavedModelBundleSourceAdapter adapte le chemin de stockage à Loader<SavedModelBundle> afin que la durée de vie du modèle puisse être gérée par Manager . Veuillez noter que SavedModelBundle est le successeur de SessionBundle obsolète. Les utilisateurs sont encouragés à utiliser SavedModelBundle car la prise en charge de SessionBundle sera bientôt supprimée.

Avec tout cela, ServerCore effectue en interne les opérations suivantes :

  • Instancie un FileSystemStoragePathSource qui surveille les chemins d'exportation de modèles déclarés dans model_config_list .
  • Instancie un SourceAdapter à l'aide de PlatformConfigMap avec la plate-forme de modèle déclarée dans model_config_list et y connecte le FileSystemStoragePathSource . De cette façon, chaque fois qu'une nouvelle version du modèle est découverte sous le chemin d'exportation, SavedModelBundleSourceAdapter l'adapte à un Loader<SavedModelBundle> .
  • Instancie une implémentation spécifique de Manager appelée AspiredVersionsManager qui gère toutes ces instances Loader créées par SavedModelBundleSourceAdapter . ServerCore exporte l'interface Manager en déléguant les appels à AspiredVersionsManager .

Chaque fois qu'une nouvelle version est disponible, cet AspiredVersionsManager charge la nouvelle version et, selon son comportement par défaut, décharge l'ancienne. Si vous souhaitez commencer la personnalisation, nous vous encourageons à comprendre les composants créés en interne et comment les configurer.

Il convient de mentionner que TensorFlow Serving est conçu dès le départ pour être très flexible et extensible. Vous pouvez créer divers plugins pour personnaliser le comportement du système, tout en tirant parti des composants de base génériques tels que ServerCore et AspiredVersionsManager . Par exemple, vous pouvez créer un plugin de source de données qui surveille le stockage dans le cloud au lieu du stockage local, ou vous pouvez créer un plugin de stratégie de version qui effectue la transition de version d'une manière différente. En fait, vous pouvez même créer un plugin de modèle personnalisé qui sert modèles non TensorFlow. Ces sujets sortent du cadre de ce didacticiel. Cependant, vous pouvez vous référer aux didacticiels sur la source personnalisée et sur le serveur personnalisé pour plus d'informations.

Traitement par lots

Une autre fonctionnalité de serveur typique que nous souhaitons dans un environnement de production est le traitement par lots. Les accélérateurs matériels modernes (GPU, etc.) utilisés pour effectuer l'inférence d'apprentissage automatique atteignent généralement la meilleure efficacité de calcul lorsque les requêtes d'inférence sont exécutées par lots importants.

Le traitement par lots peut être activé en fournissant SessionBundleConfig approprié lors de la création du SavedModelBundleSourceAdapter . Dans ce cas, nous définissons les BatchingParameters avec des valeurs par défaut. Le traitement par lots peut être affiné en définissant des valeurs de délai d'attente personnalisées, de taille de lot, etc. Pour plus de détails, veuillez vous référer à BatchingParameters .

SessionBundleConfig session_bundle_config;
// Batching config
if (enable_batching) {
  BatchingParameters* batching_parameters =
      session_bundle_config.mutable_batching_parameters();
  batching_parameters->mutable_thread_pool_name()->set_value(
      "model_server_batch_threads");
}
*saved_model_bundle_source_adapter_config.mutable_legacy_config() =
    session_bundle_config;

Une fois le lot complet atteint, les requêtes d'inférence sont fusionnées en interne en une seule requête volumineuse (tenseur) et tensorflow::Session::Run() est invoqué (d'où vient le gain d'efficacité réel sur les GPU).

Servir avec le gestionnaire

Comme mentionné ci-dessus, TensorFlow Serving Manager est conçu pour être un composant générique capable de gérer le chargement, le service, le déchargement et la transition de version de modèles générés par des systèmes d'apprentissage automatique arbitraires. Ses API sont construites autour des concepts clés suivants :

  • Serviable : Servable est tout objet opaque qui peut être utilisé pour répondre aux demandes des clients. La taille et la granularité d'un servable sont flexibles, de sorte qu'un seul servable peut inclure n'importe quoi, depuis un seul fragment d'une table de recherche jusqu'à un seul modèle appris par machine ou un tuple de modèles. Un servable peut être de n’importe quel type et interface.

  • Version servable : les servables sont versionnés et TensorFlow Serving Manager peut gérer une ou plusieurs versions d'un servable. La gestion des versions permet de charger simultanément plusieurs versions d'un serveur, ce qui permet un déploiement et une expérimentation progressifs.

  • Serviable Stream : Un flux servable est la séquence de versions d'un servable, avec des numéros de version croissants.

  • Modèle : Un modèle appris automatiquement est représenté par un ou plusieurs servables. Des exemples de servables sont :

    • Session TensorFlow ou wrappers autour d'eux, tels que SavedModelBundle .
    • Autres types de modèles appris par machine.
    • Tableaux de recherche de vocabulaire.
    • Intégration de tables de recherche.

    Un modèle composite peut être représenté sous la forme de plusieurs servables indépendants ou sous la forme d'un seul servable composite. Un servable peut également correspondre à une fraction d'un modèle, par exemple avec une grande table de recherche répartie sur de nombreuses instances Manager .

Pour mettre tout cela dans le contexte de ce tutoriel :

  • Les modèles TensorFlow sont représentés par un type de servable : SavedModelBundle . SavedModelBundle se compose en interne d'un tensorflow:Session associé à des métadonnées sur le graphique chargé dans la session et comment l'exécuter pour l'inférence.

  • Il existe un répertoire du système de fichiers contenant un flux d'exportations TensorFlow, chacune dans son propre sous-répertoire dont le nom est un numéro de version. Le répertoire externe peut être considéré comme la représentation sérialisée du flux utilisable pour le modèle TensorFlow diffusé. Chaque export correspond à une servable pouvant être chargée.

  • AspiredVersionsManager surveille le flux d'exportation et gère dynamiquement le cycle de vie de tous les servables SavedModelBundle .

TensorflowPredictImpl::Predict alors simplement :

  • Demande SavedModelBundle au gestionnaire (via ServerCore).
  • Utilise les generic signatures pour mapper les noms de tenseurs logiques dans PredictRequest aux noms de tenseurs réels et lier les valeurs aux tenseurs.
  • Exécute l'inférence.

Testez et exécutez le serveur

Copiez la première version de l'export dans le dossier surveillé :

mkdir /tmp/monitored
cp -r /tmp/mnist/1 /tmp/monitored

Démarrez ensuite le serveur :

docker run -p 8500:8500 \
  --mount type=bind,source=/tmp/monitored,target=/models/mnist \
  -t --entrypoint=tensorflow_model_server tensorflow/serving --enable_batching \
  --port=8500 --model_name=mnist --model_base_path=/models/mnist &

Le serveur émettra des messages de journal toutes les secondes indiquant "Version en attente pour servable...", ce qui signifie qu'il a trouvé l'exportation et suit son existence continue.

Exécutons le client avec --concurrency=10 . Cela enverra des requêtes simultanées au serveur et déclenchera ainsi votre logique de traitement par lots.

tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Ce qui donne un résultat qui ressemble à :

...
Inference error rate: 13.1%

Ensuite, nous copions la deuxième version de l'export dans le dossier surveillé et réexécutons le test :

cp -r /tmp/mnist/2 /tmp/monitored
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Ce qui donne un résultat qui ressemble à :

...
Inference error rate: 9.5%

Cela confirme que votre serveur découvre automatiquement la nouvelle version et l'utilise pour le service !