Treten Sie der SIG TFX-Addons-Community bei und helfen Sie mit, TFX noch besser zu machen! SIG TFX-Addons beitreten

Eine neue Art von Servable erstellen

In diesem Dokument wird erläutert, wie Sie TensorFlow Serving um eine neue Art von Servable erweitern können. Der bekannteste servable-Typ ist SavedModelBundle . Es kann jedoch nützlich sein, andere Arten von servables zu definieren, um Daten zu liefern, die zu Ihrem Modell passen. Beispiele hierfür sind: eine Vokabular-Nachschlagetabelle und eine Feature-Transformationslogik. Jede C ++ - Klasse kann eine bedienbare Klasse sein, z. B. int , std::map<string, int> oder eine in Ihrer Binärdatei definierte Klasse - nennen wir sie YourServable .

Definieren eines Loader und SourceAdapter für YourServable

Damit TensorFlow Serving YourServable verwalten und YourServable , müssen Sie zwei Dinge definieren:

  1. Eine Loader Klasse, die eine Instanz von YourServable lädt, Zugriff darauf gewährt und entlädt.

  2. Ein SourceAdapter , der Loader aus einem zugrunde liegenden Datenformat instanziiert, z. B. aus Dateisystempfaden. Alternativ zu einem SourceAdapter können Sie eine vollständige Source schreiben. Da der SourceAdapter Ansatz jedoch häufiger und modularer ist, konzentrieren wir uns hier darauf.

Die Loader Abstraktion ist in core/loader.h . Sie müssen Methoden zum Laden, Zugreifen auf und Entladen Ihrer Art von Servable definieren. Die Daten, von denen der Servable geladen wird, können von überall kommen, aber es ist üblich, dass sie von einem Speichersystempfad stammen. Nehmen wir an, dass dies bei YourServable der Fall YourServable . Nehmen wir weiter an, Sie haben bereits eine Source<StoragePath> , mit der Sie zufrieden sind (wenn nicht, Source<StoragePath> Sie das Dokument Benutzerdefinierte Quelle ).

Zusätzlich zu Ihrem Loader müssen Sie einen SourceAdapter definieren, der einen Loader aus einem bestimmten Speicherpfad instanziiert. Die meisten einfachen Anwendungsfälle können die beiden Objekte SimpleLoaderSourceAdapter mit der SimpleLoaderSourceAdapter Klasse (in core/simple_loader.h ) core/simple_loader.h . Erweiterte Anwendungsfälle können sich entscheiden , spezifizieren Loader und SourceAdapter Klassen separat den unteren Level - APIs verwenden, zB wenn der SourceAdapter einig Zustand zu halten braucht, und / oder wenn der Bedarf des Staates unter geteilt wird Loader - Instanzen.

Es gibt eine Referenzimplementierung eines einfachen Hashmap-Servables, das SimpleLoaderSourceAdapter in servables/hashmap/hashmap_source_adapter.cc . Möglicherweise ist es zweckmäßig, eine Kopie von HashmapSourceAdapter und diese dann an Ihre Anforderungen anzupassen.

Die Implementierung von HashmapSourceAdapter besteht aus zwei Teilen:

  1. Die Logik zum Laden einer Hashmap aus einer Datei in LoadHashmapFromFile() .

  2. Die Verwendung von SimpleLoaderSourceAdapter zum Definieren eines SourceAdapter , der Hashmap-Loader basierend auf LoadHashmapFromFile() . Der neue SourceAdapter kann aus einer Konfigurationsprotokollnachricht vom Typ HashmapSourceAdapterConfig . Derzeit enthält die Konfigurationsnachricht nur das Dateiformat, und für die Referenzimplementierung wird nur ein einziges einfaches Format unterstützt.

    Beachten Sie den Aufruf von Detach() im Destruktor. Dieser Aufruf ist erforderlich, um Rennen zwischen dem Abreißzustand und laufenden Aufrufen des Creator Lambda in anderen Threads zu vermeiden. (Obwohl dieser einfache Quelladapter keinen Status hat, erzwingt die Basisklasse dennoch, dass Detach () aufgerufen wird.)

YourServable , dass YourServable Objekte in einen Manager geladen werden

So verbinden Sie Ihren neuen SourceAdapter für YourServable Loader mit einer grundlegenden Quelle für Speicherpfade und einem Manager (mit schlechter Fehlerbehandlung; echter Code sollte vorsichtiger sein):

Erstellen Sie zunächst einen Manager:

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

Erstellen Sie dann einen YourServable und schließen Sie ihn an den Manager an:

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

Erstellen Sie zum Schluss eine einfache Pfadquelle und schließen Sie sie an Ihren Adapter an:

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());

Zugriff auf geladene YourServable Objekte

So erhalten Sie ein Handle für ein geladenes YourServable und verwenden es:

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();

Erweitert: Sorgen Sie dafür, dass mehrere wartbare Instanzen den Status gemeinsam nutzen

SourceAdapters können einen Status enthalten, der von mehreren ausgegebenen Servables gemeinsam genutzt wird. Beispielsweise:

  • Ein gemeinsam genutzter Thread-Pool oder eine andere Ressource, die von mehreren Servables verwendet wird.

  • Eine gemeinsam genutzte schreibgeschützte Datenstruktur, die von mehreren Servables verwendet wird, um den zeitlichen und räumlichen Aufwand beim Replizieren der Datenstruktur in jeder servierbaren Instanz zu vermeiden.

Der gemeinsam genutzte Status, dessen Initialisierungszeit und -größe vernachlässigbar ist (z. B. Thread-Pools), kann vom SourceAdapter eifrig erstellt werden, der dann in jeden ausgegebenen wartbaren Loader einen Zeiger darauf einbettet. Die Erstellung eines teuren oder großen gemeinsam genutzten Status sollte auf den ersten anwendbaren Aufruf von Loader :: Load () verschoben werden, dh vom Manager gesteuert. Symmetrisch sollte der Aufruf von Loader :: Unload () zum endgültigen Servable unter Verwendung des teuren / großen gemeinsam genutzten Status ihn abreißen.