Este documento explica cómo ampliar TensorFlow Serving con un nuevo tipo de servable. El tipo de servable más destacado es SavedModelBundle
, pero puede ser útil para definir otros tipos de servables, para entregar datos que vayan junto con su modelo. Los ejemplos incluyen: una tabla de búsqueda de vocabulario, lógica de transformación de características. Cualquier clase de C++ puede ser servible, por ejemplo, int
, std::map<string, int>
o cualquier clase definida en su binario; llamémosla YourServable
.
Definición de un Loader
y SourceAdapter
para YourServable
Para permitir que TensorFlow Serving administre y proporcione YourServable
, debe definir dos cosas:
Una clase
Loader
que carga, proporciona acceso y descarga una instancia deYourServable
.Un
SourceAdapter
que crea instancias de cargadores a partir de algún formato de datos subyacente, por ejemplo, rutas del sistema de archivos. Como alternativa a unSourceAdapter
, podría escribir unSource
completo. Sin embargo, dado que el enfoqueSourceAdapter
es más común y más modular, nos centraremos en él aquí.
La abstracción Loader
se define en core/loader.h
. Requiere que defina métodos para cargar, acceder y descargar su tipo de servable. Los datos desde los que se carga el servable pueden provenir de cualquier lugar, pero es común que provengan de una ruta del sistema de almacenamiento. Supongamos que ese es el caso de YourServable
. Supongamos además que ya tiene una Source<StoragePath>
con la que está satisfecho (si no, consulte el documento Fuente personalizada ).
Además de su Loader
, necesitará definir un SourceAdapter
que cree una instancia de un Loader
desde una ruta de almacenamiento determinada. La mayoría de los casos de uso simples pueden especificar los dos objetos de manera concisa con la clase SimpleLoaderSourceAdapter
(en core/simple_loader.h
). Los casos de uso avanzados pueden optar por especificar las clases Loader
y SourceAdapter
por separado utilizando las API de nivel inferior, por ejemplo, si SourceAdapter
necesita conservar algún estado y/o si el estado debe compartirse entre instancias Loader
.
Hay una implementación de referencia de un servable hashmap simple que usa SimpleLoaderSourceAdapter
en servables/hashmap/hashmap_source_adapter.cc
. Puede que le resulte conveniente hacer una copia de HashmapSourceAdapter
y luego modificarla para adaptarla a sus necesidades.
La implementación de HashmapSourceAdapter
consta de dos partes:
La lógica para cargar un mapa hash desde un archivo, en
LoadHashmapFromFile()
.El uso de
SimpleLoaderSourceAdapter
para definir unSourceAdapter
que emite cargadores de mapas hash basados enLoadHashmapFromFile()
. Se puede crear una instancia del nuevoSourceAdapter
a partir de un mensaje de protocolo de configuración de tipoHashmapSourceAdapterConfig
. Actualmente, el mensaje de configuración contiene solo el formato de archivo y, a efectos de la implementación de referencia, solo se admite un formato simple.Tenga en cuenta la llamada a
Detach()
en el destructor. Esta llamada es necesaria para evitar carreras entre el estado de destrucción y cualquier invocación en curso del Creador lambda en otros hilos. (Aunque este adaptador de fuente simple no tiene ningún estado, la clase base exige que se llame a Detach()).
Organizar la carga de objetos YourServable
en un administrador
A continuación se explica cómo conectar su nuevo SourceAdapter
para cargadores YourServable
a una fuente básica de rutas de almacenamiento y a un administrador (con un mal manejo de errores; el código real debería tener más cuidado):
Primero, crea un administrador:
std::unique_ptr<AspiredVersionsManager> manager = ...;
Luego, cree un adaptador de fuente YourServable
y conéctelo al administrador:
auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());
Por último, cree una fuente de ruta simple y conéctela a su adaptador:
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());
Accediendo a objetos YourServable
cargados
Aquí se explica cómo obtener un identificador de un YourServable
cargado y usarlo:
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();
Avanzado: organización de múltiples instancias servibles para compartir el estado
SourceAdapters puede albergar un estado compartido entre varios servables emitidos. Por ejemplo:
Un grupo de subprocesos compartido u otro recurso que utilizan varios servidores.
Una estructura de datos compartida de solo lectura que utilizan varios servidores para evitar la sobrecarga de tiempo y espacio de replicar la estructura de datos en cada instancia de servicio.
El estado compartido cuyo tiempo y tamaño de inicialización es insignificante (por ejemplo, grupos de subprocesos) puede ser creado con entusiasmo por SourceAdapter, que luego incorpora un puntero a él en cada cargador de servicio emitido. La creación de un estado compartido costoso o grande debe diferirse hasta la primera llamada Loader::Load() aplicable, es decir, gobernada por el administrador. Simétricamente, la llamada Loader::Unload() al servidor final usando el estado compartido caro/grande debería derribarlo.