Rejoignez TensorFlow à Google I/O, du 11 au 12 mai. Inscrivez-vous maintenant

Créer un nouveau type de servable

Ce document explique comment étendre TensorFlow Serving avec un nouveau type de serveur. Le type le plus important est diffusable SavedModelBundle , mais il peut être utile de définir d' autres types de servables, pour servir des données qui va de pair avec votre modèle. Les exemples incluent : une table de recherche de vocabulaire, une logique de transformation de caractéristiques. Toute classe C ++ peut être un diffusable, par exemple int , std::map<string, int> ou une classe définie dans votre binaire - laissez - nous l' appelons YourServable .

Définition d' un Loader et SourceAdapter pour YourServable

Pour activer tensorflow Au service de gérer et servir YourServable , vous devez définir deux choses:

  1. Un Loader de classe qui se charge, donne accès à et déchargements une instance de YourServable .

  2. Un SourceAdapter qui instancie chargeurs de certains chemins de fichiers système de format de données sous - jacentes. Comme alternative à un SourceAdapter , vous pourriez écrire une complète Source . Cependant, étant donné que la SourceAdapter approche est plus fréquente et plus modulaire, nous nous concentrons sur ici.

Le Loader abstraction est défini dans la core/loader.h . Cela vous oblige à définir des méthodes de chargement, d'accès et de déchargement de votre type de servable. Les données à partir desquelles le serveur est chargé peuvent provenir de n'importe où, mais il est courant qu'elles proviennent d'un chemin de système de stockage. Supposons que est le cas pour YourServable . Admettons encore que vous avez déjà une Source<StoragePath> que vous êtes satisfait (sinon, voir la source personnalisée document).

En plus de votre Loader , vous devrez définir un SourceAdapter qui instancie un Loader d'un chemin de stockage donné. La plupart des simples cas d' utilisation peuvent spécifier les deux objets de manière concise la SimpleLoaderSourceAdapter classe (en core/simple_loader.h ). Cas d' utilisation avancés peuvent choisir de spécifier Loader et SourceAdapter les classes séparément en utilisant les API de niveau inférieur, par exemple si le SourceAdapter doit conserver un état, et / ou si les besoins de l' État à partager entre Loader cas.

Il y a une implémentation de référence d'un simple , diffusable HashMap qui utilise SimpleLoaderSourceAdapter dans servables/hashmap/hashmap_source_adapter.cc . Vous trouverez peut - être commode de faire une copie de HashmapSourceAdapter puis le modifier en fonction de vos besoins.

La mise en œuvre de HashmapSourceAdapter comporte deux parties:

  1. La logique pour charger un hashmap partir d' un fichier, dans LoadHashmapFromFile() .

  2. L'utilisation de SimpleLoaderSourceAdapter pour définir une SourceAdapter qui émet des chargeurs hashmap sur la base LoadHashmapFromFile() . Le nouveau SourceAdapter peut être instancié à partir d' un message de protocole de configuration de type HashmapSourceAdapterConfig . Actuellement, le message de configuration contient uniquement le format de fichier et, aux fins de l'implémentation de référence, un seul format simple est pris en charge.

    Notez l'appel à Detach() dans le destructor. Cet appel est nécessaire pour éviter les courses entre l'état de suppression et les invocations en cours du créateur lambda dans d'autres threads. (Même si cet adaptateur source simple n'a pas d'état, la classe de base impose néanmoins que Detach() soit appelé.)

Prendre des dispositions pour YourServable objets à charger dans un gestionnaire

Voici comment raccorder votre nouveau SourceAdapter pour YourServable chargeurs à une source de base de chemins de stockage, et un gestionnaire (avec une mauvaise gestion des erreurs, le code réel devrait être plus prudent):

Tout d'abord, créez un gestionnaire :

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

Ensuite, créez un YourServable adaptateur source et branchez - le dans le gestionnaire:

auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());

Enfin, créez un chemin source simple et branchez-le sur votre adaptateur :

std::unique_ptr<FileSystemStoragePathSource> path_source;
// Here are some FileSystemStoragePathSource config settings that ought to get
// it working, but for details please see its documentation.
FileSystemStoragePathSourceConfig config;
// We just have a single servable stream. Call it "default".
config.set_servable_name("default");
config.set_base_path(FLAGS::base_path /* base path for our servable files */);
config.set_file_system_poll_wait_seconds(1);
TF_CHECK_OK(FileSystemStoragePathSource::Create(config, &path_source));
ConnectSourceToTarget(path_source.get(), your_adapter.get());

Accès chargés YourServable objets

Voici comment obtenir une poignée à une charge YourServable , et l' utiliser:

auto handle_request = serving::ServableRequest::Latest("default");
ServableHandle<YourServable*> servable;
Status status = manager->GetServableHandle(handle_request, &servable);
if (!status.ok()) {
  LOG(INFO) << "Zero versions of 'default' servable have been loaded so far";
  return;
}
// Use the servable.
(*servable)->SomeYourServableMethod();

Avancé : organiser le partage de l'état de plusieurs instances pouvant être servies

Les SourceAdapters peuvent héberger un état partagé entre plusieurs servables émis. Par example:

  • Un pool de threads partagé ou une autre ressource utilisée par plusieurs servables.

  • Une structure de données partagée en lecture seule que plusieurs servables utilisent, pour éviter la surcharge de temps et d'espace liée à la réplication de la structure de données dans chaque instance servable.

L'état partagé dont le temps d'initialisation et la taille sont négligeables (par exemple les pools de threads) peut être créé avec empressement par le SourceAdapter, qui intègre ensuite un pointeur vers celui-ci dans chaque chargeur de serveur émis. La création d'un état partagé coûteux ou volumineux doit être reportée au premier appel Loader::Load() applicable, c'est-à-dire régie par le gestionnaire. Symétriquement, l'appel Loader::Unload() au serveur final utilisant l'état partagé cher/grand devrait le détruire.