В этом документе объясняется, как расширить обслуживание TensorFlow с помощью нового типа обслуживаемых объектов. Наиболее известным типом обслуживаемых объектов является SavedModelBundle , но он может быть полезен для определения других типов обслуживаемых объектов для обслуживания данных, соответствующих вашей модели. Примеры включают: словарную таблицу поиска, логику преобразования функций. Любой класс C++ может быть обслуживаемым, например int , std::map<string, int> или любой класс, определенный в вашем двоичном файле — назовем его YourServable .
Определение Loader и SourceAdapter для YourServable
Чтобы включить TensorFlow Serving для управления и обслуживания YourServable , вам необходимо определить две вещи:
Класс
Loader, который загружает, предоставляет доступ и выгружает экземплярYourServable.SourceAdapter, который создает экземпляры загрузчиков из некоторого базового формата данных, например путей файловой системы. В качестве альтернативыSourceAdapterвы можете написать полныйSource. Однако, поскольку подходSourceAdapterболее распространен и более модульен, мы сосредоточимся здесь на нем.
Абстракция Loader определена в core/loader.h . Для этого вам необходимо определить методы для загрузки, доступа и выгрузки вашего типа обслуживаемого объекта. Данные, из которых загружается обслуживаемый объект, могут поступать откуда угодно, но обычно они поступают из пути системы хранения. Предположим, что это относится к YourServable . Далее предположим, что у вас уже есть Source<StoragePath> , который вас устраивает (если нет, см. документ «Пользовательский исходный код» ).
В дополнение к вашему Loader вам нужно будет определить SourceAdapter , который создает экземпляр Loader из заданного пути к хранилищу. В большинстве простых вариантов использования два объекта можно указать кратко с помощью класса SimpleLoaderSourceAdapter (в core/simple_loader.h ). В расширенных сценариях использования можно выбрать отдельное указание классов Loader и SourceAdapter с использованием API более низкого уровня, например, если SourceAdapter необходимо сохранять некоторое состояние и/или если состояние необходимо разделить между экземплярами Loader .
В servables/hashmap/hashmap_source_adapter.cc существует эталонная реализация простого обслуживаемого хеш-карты, использующая SimpleLoaderSourceAdapter . Возможно, вам будет удобно сделать копию HashmapSourceAdapter , а затем изменить ее в соответствии со своими потребностями.
Реализация HashmapSourceAdapter состоит из двух частей:
Логика загрузки хэш-карты из файла в
LoadHashmapFromFile().Использование
SimpleLoaderSourceAdapterдля определенияSourceAdapter, который создает загрузчики хэш-карт на основеLoadHashmapFromFile(). НовыйSourceAdapterможет быть создан из сообщения протокола конфигурации типаHashmapSourceAdapterConfig. В настоящее время сообщение конфигурации содержит только формат файла, и для эталонной реализации поддерживается только один простой формат.Обратите внимание на вызов
Detach()в деструкторе. Этот вызов необходим, чтобы избежать гонок между удалением состояния и любыми текущими вызовами лямбды Creator в других потоках. (Несмотря на то, что этот простой исходный адаптер не имеет никакого состояния, базовый класс, тем не менее, обеспечивает вызов Detach().)
Организация загрузки объектов YourServable в менеджере
Вот как подключить ваш новый SourceAdapter для загрузчиков YourServable к базовому источнику путей хранения и менеджеру (с плохой обработкой ошибок; реальный код должен быть более осторожным):
Сначала создайте менеджера:
std::unique_ptr<AspiredVersionsManager> manager = ...;
Затем создайте исходный адаптер YourServable и подключите его к менеджеру:
auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());
Наконец, создайте простой источник пути и подключите его к адаптеру:
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());
Доступ к загруженным объектам YourServable
Вот как получить дескриптор загруженного YourServable и использовать его:
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();
Дополнительно: организация нескольких обслуживаемых экземпляров для совместного использования состояния.
SourceAdapters может содержать состояние, которое используется несколькими исходящими обслуживаемыми объектами. Например:
Общий пул потоков или другой ресурс, который используют несколько обслуживаемых объектов.
Общая структура данных, доступная только для чтения, которую используют несколько обслуживаемых объектов, чтобы избежать затрат времени и пространства на репликацию структуры данных в каждом обслуживаемом экземпляре.
Общее состояние, время и размер инициализации которого незначительны (например, пулы потоков), может быть легко создано SourceAdapter, который затем встраивает указатель на него в каждый созданный обслуживаемый загрузчик. Создание дорогостоящего или большого общего состояния должно быть отложено до первого применимого вызова Loader::Load(), т.е. управляться менеджером. Симметрично, вызов Loader::Unload() конечного обслуживаемого объекта с использованием дорогого/большого общего состояния должен его разрушить.