В этом документе объясняется, как расширить обслуживание 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() конечного обслуживаемого объекта с использованием дорогого/большого общего состояния должен его разрушить.