Crear un módulo que descubra nuevas rutas de servicio

Este documento explica cómo ampliar TensorFlow Serving para monitorear diferentes sistemas de almacenamiento para descubrir nuevos (versiones de) modelos o datos para servir. En particular, cubre cómo crear y utilizar un módulo que monitorea una ruta del sistema de almacenamiento para detectar la aparición de nuevas subrutas, donde cada subruta representa una nueva versión disponible para cargar. Ese tipo de módulo se llama Source<StoragePath> , porque emite objetos de tipo StoragePath (con tipo de string ). Se puede componer con un SourceAdapter que crea un Loader útil a partir de una ruta determinada que descubre la fuente.

Primero, una nota sobre la generalidad.

No es necesario utilizar rutas como identificadores de datos que se pueden servir; simplemente ilustra una forma de ingerir servicios en el sistema. Incluso si su entorno no encapsula datos que se pueden servir en rutas, este documento lo familiarizará con las abstracciones clave. Tiene la opción de crear módulos Source<T> y SourceAdapter<T1, T2> para tipos que se adapten a su entorno (por ejemplo, mensajes RPC o pub/sub, registros de bases de datos), o simplemente crear un Source<std::unique_ptr<Loader>> monolítico. Source<std::unique_ptr<Loader>> que emite cargadores servibles directamente.

Por supuesto, cualquiera que sea el tipo de datos que emita su fuente (ya sean rutas POSIX, rutas de Google Cloud Storage o identificadores RPC), es necesario que haya módulos adjuntos que puedan cargar servables en función de eso. Estos módulos se denominan SourceAdapters . La creación de uno personalizado se describe en el documento Servible personalizado . TensorFlow Serving viene con uno para crear instancias de sesiones de TensorFlow basadas en rutas en sistemas de archivos que admite TensorFlow. Se puede agregar soporte para sistemas de archivos adicionales a TensorFlow extendiendo la abstracción RandomAccessFile ( tensorflow/core/public/env.h ).

Este documento se centra en la creación de una fuente que emita rutas en un sistema de archivos compatible con TensorFlow. Termina con un tutorial sobre cómo usar su fuente junto con módulos preexistentes para servir modelos de TensorFlow.

Creando tu fuente

Tenemos una implementación de referencia de Source<StoragePath> , llamada FileSystemStoragePathSource (en sources/storage_path/file_system_storage_path_source* ). FileSystemStoragePathSource monitorea una ruta particular del sistema de archivos, busca subdirectorios numéricos e informa el último de ellos como la versión que aspira a cargar. Este documento recorre los aspectos más destacados de FileSystemStoragePathSource . Puede que le resulte conveniente hacer una copia de FileSystemStoragePathSource y luego modificarla para adaptarla a sus necesidades.

Primero, FileSystemStoragePathSource implementa la API Source<StoragePath> , que es una especialización de la API Source<T> con T vinculado a StoragePath . La API consta de un único método SetAspiredVersionsCallback() , que proporciona un cierre que la fuente puede invocar para comunicar que desea que se cargue un conjunto particular de versiones servibles.

FileSystemStoragePathSource utiliza la devolución de llamada de versiones aspiradas de una manera muy simple: inspecciona periódicamente el sistema de archivos (haciendo un ls , esencialmente), y si encuentra una o más rutas que parecen versiones que se pueden servir, determina cuál es la última versión e invoca la devolución de llamada con una lista de tamaño uno que contiene solo esa versión (bajo la configuración predeterminada). Entonces, en cualquier momento dado, FileSystemStoragePathSource solicita que se cargue como máximo un servable, y su implementación aprovecha la idempotencia de la devolución de llamada para mantenerse sin estado (no hay nada de malo en invocar la devolución de llamada repetidamente con los mismos argumentos).

FileSystemStoragePathSource tiene una fábrica de inicialización estática (el método Create() ), que recibe un mensaje de protocolo de configuración. El mensaje de configuración incluye detalles como la ruta base para monitorear y el intervalo de monitoreo. También incluye el nombre de la secuencia de servicio que se va a emitir. (Los enfoques alternativos podrían extraer el nombre del flujo de servicio de la ruta base, para emitir múltiples flujos de servicio basados ​​en la observación de una jerarquía de directorios más profunda; esas variantes están más allá del alcance de la implementación de referencia).

La mayor parte de la implementación consiste en un hilo que examina periódicamente el sistema de archivos, junto con cierta lógica para identificar y ordenar cualquier subruta numérica que descubra. El hilo se inicia dentro de SetAspiredVersionsCallback() (no en Create() ) porque ese es el punto en el que la fuente debe "comenzar" y sabe dónde enviar solicitudes de versión aspirada.

Usando su fuente para cargar sesiones de TensorFlow

Es probable que quieras usar tu nuevo módulo fuente junto con SavedModelBundleSourceAdapter ( servables/tensorflow/saved_model_bundle_source_adapter* ), que interpretará cada ruta que emite tu fuente como una exportación de TensorFlow y convertirá cada ruta en un cargador para un servidor TensorFlow SavedModelBundle . Probablemente conecte el adaptador SavedModelBundle a un AspiredVersionsManager , que se encarga de cargar y servir los servables. Un buen ejemplo de cómo encadenar estos tres tipos de módulos para obtener una biblioteca de servidor que funcione se encuentra en servables/tensorflow/simple_servers.cc . A continuación se muestra un recorrido por el flujo de código principal (con un manejo incorrecto 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 fuente SavedModelBundle y conéctelo al administrador:

std::unique_ptr<SavedModelBundleSourceAdapter> bundle_adapter;
SavedModelBundleSourceAdapterConfig config;
// ... populate 'config' with TensorFlow options.
TF_CHECK_OK(SavedModelBundleSourceAdapter::Create(config, &bundle_adapter));
ConnectSourceToTarget(bundle_adapter.get(), manager.get());

Por último, cree su fuente de ruta y conéctela al adaptador SavedModelBundle :

auto your_source = new YourPathSource(...);
ConnectSourceToTarget(your_source, bundle_adapter.get());

La función ConnectSourceToTarget() (definida en core/target.h ) simplemente invoca SetAspiredVersionsCallback() para conectar un Source<T> a un Target<T> (un Target es un módulo que captura solicitudes de versión aspirada, es decir, un adaptador o administrador ).