W tym dokumencie wyjaśniono, jak rozszerzyć obsługę TensorFlow o nowy rodzaj obiektu udostępnianego. Najbardziej znanym typem serwowalnym jest SavedModelBundle
, ale przydatne może być zdefiniowanie innych rodzajów obiektów serwowalnych, aby udostępniać dane pasujące do Twojego modelu. Przykłady obejmują: tabelę wyszukiwania słownictwa, logikę transformacji funkcji. Dowolna klasa C++ może być obsługiwana, np. int
, std::map<string, int>
lub dowolna klasa zdefiniowana w pliku binarnym — nazwijmy ją YourServable
.
Definiowanie Loader
i SourceAdapter
dla YourServable
Aby umożliwić TensorFlow Serving zarządzanie i obsługę YourServable
, musisz zdefiniować dwie rzeczy:
Klasa
Loader
, która ładuje, zapewnia dostęp i rozładowuje instancjęYourServable
.SourceAdapter
, który tworzy instancje modułów ładujących z pewnego podstawowego formatu danych, np. ścieżek systemu plików. Alternatywą dlaSourceAdapter
jest możliwość napisania kompletnegoSource
. Ponieważ jednak podejścieSourceAdapter
jest bardziej powszechne i bardziej modułowe, skupiamy się na nim tutaj.
Abstrakcja Loader
jest zdefiniowana w core/loader.h
. Wymaga zdefiniowania metod ładowania, uzyskiwania dostępu i rozładowywania typu serwowalnego. Dane, z których ładowany jest obiekt servable, mogą pochodzić z dowolnego miejsca, ale często pochodzą ze ścieżki systemu pamięci masowej. Załóżmy, że tak jest w przypadku YourServable
. Załóżmy dalej, że masz już Source<StoragePath>
, z którego jesteś zadowolony (jeśli nie, zobacz dokument Niestandardowe źródło ).
Oprócz Loader
będziesz musiał zdefiniować SourceAdapter
, który utworzy instancję Loader
z danej ścieżki przechowywania. W większości prostych przypadków użycia można zwięźle określić dwa obiekty za pomocą klasy SimpleLoaderSourceAdapter
(w core/simple_loader.h
). Zaawansowane przypadki użycia mogą zdecydować się na oddzielne określenie klas Loader
i SourceAdapter
przy użyciu interfejsów API niższego poziomu, np. jeśli SourceAdapter
musi zachować jakiś stan i/lub jeśli stan musi być współdzielony pomiędzy instancjami Loader
.
Istnieje referencyjna implementacja prostego serwable hashmap, która używa SimpleLoaderSourceAdapter
w servables/hashmap/hashmap_source_adapter.cc
. Może się okazać, że wygodnie będzie utworzyć kopię HashmapSourceAdapter
, a następnie zmodyfikować ją tak, aby odpowiadała Twoim potrzebom.
Implementacja HashmapSourceAdapter
składa się z dwóch części:
Logika ładowania mapy mieszającej z pliku w
LoadHashmapFromFile()
.Użycie
SimpleLoaderSourceAdapter
do zdefiniowaniaSourceAdapter
, który emituje moduły ładujące hashmap w oparciu oLoadHashmapFromFile()
. NowąSourceAdapter
można utworzyć z komunikatu protokołu konfiguracji typuHashmapSourceAdapterConfig
. Obecnie komunikat konfiguracyjny zawiera tylko format pliku, a na potrzeby implementacji referencyjnej obsługiwany jest tylko jeden prosty format.Zwróć uwagę na wywołanie
Detach()
w destruktorze. To wywołanie jest wymagane, aby uniknąć wyścigów między zniszczeniem stanu a wszelkimi ciągłymi wywołaniami lambdy Stwórcy w innych wątkach. (Chociaż ten prosty adapter źródłowy nie ma żadnego stanu, klasa bazowa mimo wszystko wymusza wywołanie funkcji Detach().)
Organizowanie ładowania obiektów YourServable
do menedżera
Oto jak podłączyć nowe moduły ładujące SourceAdapter
for YourServable
do podstawowego źródła ścieżek pamięci i menedżera (ze złą obsługą błędów; prawdziwy kod powinien być ostrożniejszy):
Najpierw utwórz menedżera:
std::unique_ptr<AspiredVersionsManager> manager = ...;
Następnie utwórz adapter źródłowy YourServable
i podłącz go do menedżera:
auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());
Na koniec utwórz proste źródło ścieżki i podłącz je do adaptera:
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());
Dostęp do załadowanych obiektów YourServable
Oto jak uzyskać uchwyt do załadowanego YourServable
i użyć go:
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();
Zaawansowane: organizowanie wielu obsługiwanych instancji w celu współdzielenia stanu
SourceAdapters może przechowywać stan, który jest współdzielony pomiędzy wieloma emitowanymi obiektami serwable. Na przykład:
Wspólna pula wątków lub inny zasób, z którego korzysta wiele obiektów serwowalnych.
Współdzielona struktura danych tylko do odczytu, z której korzysta wiele obiektów serwowalnych, aby uniknąć narzutu czasowego i przestrzennego związanego z replikacją struktury danych w każdej udostępnianej instancji.
Stan współdzielony, którego czas inicjalizacji i rozmiar są znikome (np. pule wątków), może zostać chętnie utworzony przez SourceAdapter, który następnie osadza wskaźnik do niego w każdym wyemitowanym obsługiwanym programie ładującym. Utworzenie kosztownego lub dużego stanu współdzielonego powinno zostać odroczone do pierwszego odpowiedniego wywołania Loader::Load(), tj. zarządzanego przez menedżera. Symetrycznie, wywołanie Loader::Unload() do końcowego obiektu obsłużonego przy użyciu drogiego/dużego stanu współdzielonego powinno go zniszczyć.