Создание нового типа обслуживаемых объектов

В этом документе объясняется, как расширить обслуживание TensorFlow с помощью нового типа обслуживаемых объектов. Наиболее известным типом обслуживаемых объектов является SavedModelBundle , но он может быть полезен для определения других типов обслуживаемых объектов для обслуживания данных, соответствующих вашей модели. Примеры включают: словарную таблицу поиска, логику преобразования функций. Любой класс C++ может быть обслуживаемым, например int , std::map<string, int> или любой класс, определенный в вашем двоичном файле — назовем его YourServable .

Определение Loader и SourceAdapter для YourServable

Чтобы включить TensorFlow Serving для управления и обслуживания YourServable , вам необходимо определить две вещи:

  1. Класс Loader , который загружает, предоставляет доступ и выгружает экземпляр YourServable .

  2. SourceAdapter , который создает экземпляры загрузчиков из некоторого базового формата данных, например путей файловой системы. В качестве альтернативы SourceAdapter вы можете написать полный Source . Однако, поскольку подход SourceAdapter более распространен и более модульен, мы сосредоточимся здесь на нем.

Абстракция Loader определена в core/loader.h . Это требует от вас определения методов для загрузки, доступа и выгрузки вашего типа обслуживаемого объекта. Данные, из которых загружается обслуживаемый объект, могут поступать откуда угодно, но обычно они поступают из пути системы хранения. Предположим, что это относится к YourServable . Далее предположим, что у вас уже есть Source<StoragePath> , который вас устраивает (если нет, см. документ «Пользовательский исходный код» ).

В дополнение к вашему Loader вам нужно будет определить SourceAdapter , который создает экземпляр Loader из заданного пути к хранилищу. В большинстве простых случаев использования два объекта можно указать кратко с помощью класса SimpleLoaderSourceAdaptercore/simple_loader.h ). В расширенных сценариях использования можно выбрать отдельное указание классов Loader и SourceAdapter с использованием API более низкого уровня, например, если SourceAdapter необходимо сохранять некоторое состояние и/или если состояние необходимо разделить между экземплярами Loader .

В servables/hashmap/hashmap_source_adapter.cc существует эталонная реализация простого обслуживаемого хеш-карты, использующая SimpleLoaderSourceAdapter . Возможно, вам будет удобно сделать копию HashmapSourceAdapter , а затем изменить ее в соответствии со своими потребностями.

Реализация HashmapSourceAdapter состоит из двух частей:

  1. Логика загрузки хэш-карты из файла в LoadHashmapFromFile() .

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