Création d'un module qui découvre de nouveaux chemins utilisables

Ce document explique comment étendre TensorFlow Serving pour surveiller différents systèmes de stockage afin de découvrir de nouveaux (versions de) modèles ou données à servir. En particulier, il explique comment créer et utiliser un module qui surveille un chemin de système de stockage pour l'apparition de nouveaux sous-chemins, où chaque sous-chemin représente une nouvelle version servable à charger. Ce type de module est appelé Source<StoragePath> , car il émet des objets de type StoragePath (typedéf en string ). Il peut être composé avec un SourceAdapter qui crée un Loader servable à partir d'un chemin donné découvert par la source.

Tout d'abord, une note sur la généralité

L’utilisation de chemins comme handles vers des données utilisables n’est pas obligatoire ; il illustre simplement une façon d'ingérer des servables dans le système. Même si votre environnement n'encapsule pas les données utilisables dans des chemins, ce document vous familiarisera avec les abstractions clés. Vous avez la possibilité de créer des modules Source<T> et SourceAdapter<T1, T2> pour les types adaptés à votre environnement (par exemple, messages RPC ou pub/sub, enregistrements de base de données), ou simplement de créer un Source<std::unique_ptr<Loader>> monolithique. Source<std::unique_ptr<Loader>> qui émet directement des chargeurs utilisables.

Bien sûr, quel que soit le type de données émises par votre source (qu'il s'agisse de chemins POSIX, de chemins Google Cloud Storage ou de handles RPC), il doit y avoir des modules d'accompagnement capables de charger des servables sur cette base. Ces modules sont appelés SourceAdapters . La création d'un serveur personnalisé est décrite dans le document Custom Serviable . TensorFlow Serving en est livré avec un pour instancier des sessions TensorFlow en fonction des chemins dans les systèmes de fichiers pris en charge par TensorFlow. On peut ajouter la prise en charge de systèmes de fichiers supplémentaires à TensorFlow en étendant l'abstraction RandomAccessFile ( tensorflow/core/public/env.h ).

Ce document se concentre sur la création d'une source qui émet des chemins dans un système de fichiers pris en charge par TensorFlow. Il se termine par une présentation pas à pas de la façon d'utiliser votre source en conjonction avec des modules préexistants pour servir les modèles TensorFlow.

Créer votre source

Nous avons une implémentation de référence d'un Source<StoragePath> , appelée FileSystemStoragePathSourcesources/storage_path/file_system_storage_path_source* ). FileSystemStoragePathSource surveille un chemin de système de fichiers particulier, surveille les sous-répertoires numériques et signale le dernier d'entre eux comme version qu'il aspire à charger. Ce document passe en revue les aspects saillants de FileSystemStoragePathSource . Vous trouverez peut-être pratique de faire une copie de FileSystemStoragePathSource , puis de la modifier en fonction de vos besoins.

Tout d’abord, FileSystemStoragePathSource implémente l’API Source<StoragePath> , qui est une spécialisation de l’API Source<T> avec T lié à StoragePath . L'API se compose d'une seule méthode SetAspiredVersionsCallback() , qui fournit une fermeture que la source peut invoquer pour communiquer qu'elle souhaite qu'un ensemble particulier de versions servables soit chargé.

FileSystemStoragePathSource utilise le rappel aspired-versions d'une manière très simple : il inspecte périodiquement le système de fichiers (en effectuant un ls , essentiellement), et s'il trouve un ou plusieurs chemins qui ressemblent à des versions servables, il détermine laquelle est la dernière version et invoque le rappel avec une liste de taille un contenant uniquement cette version (sous la configuration par défaut). Ainsi, à tout moment, FileSystemStoragePathSource demande le chargement d'au plus un servable, et son implémentation profite de l'idempotence du rappel pour rester apatride (il n'y a aucun mal à invoquer le rappel à plusieurs reprises avec les mêmes arguments).

FileSystemStoragePathSource possède une fabrique d'initialisation statique (la méthode Create() ), qui prend un message de protocole de configuration. Le message de configuration comprend des détails tels que le chemin de base à surveiller et l'intervalle de surveillance. Il inclut également le nom du flux servable à émettre. (Des approches alternatives pourraient extraire le nom du flux servable du chemin de base, pour émettre plusieurs flux servables en fonction de l'observation d'une hiérarchie de répertoires plus profonde ; ces variantes dépassent la portée de l'implémentation de référence.)

La majeure partie de l'implémentation consiste en un thread qui examine périodiquement le système de fichiers, ainsi qu'en une certaine logique pour identifier et trier tous les sous-chemins numériques qu'il découvre. Le thread est lancé dans SetAspiredVersionsCallback() (pas dans Create() ) car c'est le point auquel la source doit "démarrer" et sait où envoyer les demandes de version aspirée.

Utiliser votre source pour charger des sessions TensorFlow

Vous souhaiterez probablement utiliser votre nouveau module source en conjonction avec SavedModelBundleSourceAdapter ( servables/tensorflow/saved_model_bundle_source_adapter* ), qui interprétera chaque chemin émis par votre source comme une exportation TensorFlow et convertira chaque chemin en chargeur pour un servable TensorFlow SavedModelBundle . Vous brancherez probablement l'adaptateur SavedModelBundle dans un AspiredVersionsManager , qui se charge de charger et de servir les servables. Une bonne illustration de l'enchaînement de ces trois types de modules pour obtenir une bibliothèque de serveur fonctionnelle se trouve dans servables/tensorflow/simple_servers.cc . Voici un aperçu du flux de code principal (avec une mauvaise gestion des erreurs ; le vrai code devrait être plus prudent) :

Tout d'abord, créez un gestionnaire :

std::unique_ptr<AspiredVersionsManager> manager = ...;

Ensuite, créez un adaptateur source SavedModelBundle et branchez-le au gestionnaire :

std::unique_ptr<SavedModelBundleSourceAdapter> bundle_adapter;
SavedModelBundleSourceAdapterConfig config;
// ... populate 'config' with TensorFlow options.
TF_CHECK_OK(SavedModelBundleSourceAdapter::Create(config, &bundle_adapter));
ConnectSourceToTarget(bundle_adapter.get(), manager.get());

Enfin, créez votre chemin source et branchez-le sur l'adaptateur SavedModelBundle :

auto your_source = new YourPathSource(...);
ConnectSourceToTarget(your_source, bundle_adapter.get());

La fonction ConnectSourceToTarget() (définie dans core/target.h ) appelle simplement SetAspiredVersionsCallback() pour connecter un Source<T> à un Target<T> (un Target est un module qui intercepte les requêtes de version aspirée, c'est-à-dire un adaptateur ou un gestionnaire ).