Construcción de TensorFlow ModelServer estándar

Este tutorial le muestra cómo utilizar los componentes de TensorFlow Serving para crear el TensorFlow ModelServer estándar que descubre y ofrece dinámicamente nuevas versiones de un modelo de TensorFlow entrenado. Si solo desea utilizar el servidor estándar para servir sus modelos, consulte el tutorial básico de TensorFlow Serving .

Este tutorial utiliza el modelo de regresión Softmax simple introducido en el tutorial de TensorFlow para la clasificación de imágenes escritas a mano (datos MNIST). Si no sabe qué es TensorFlow o MNIST, consulte el tutorial MNIST para principiantes de ML .

El código de este tutorial consta de dos partes:

  • Un archivo Python mnist_saved_model.py que entrena y exporta múltiples versiones del modelo.

  • Un archivo C++ main.cc , que es el TensorFlow ModelServer estándar que descubre nuevos modelos exportados y ejecuta un servicio gRPC para brindarlos.

Este tutorial recorre las siguientes tareas:

  1. Entrene y exporte un modelo de TensorFlow.
  2. Administre el control de versiones del modelo con TensorFlow Serving ServerCore .
  3. Configure el procesamiento por lotes mediante SavedModelBundleSourceAdapterConfig .
  4. Servir solicitud con TensorFlow Serving ServerCore .
  5. Ejecute y pruebe el servicio.

Antes de comenzar, primero instale Docker

Entrenar y exportar el modelo TensorFlow

Primero, si aún no lo has hecho, clona este repositorio en tu máquina local:

git clone https://github.com/tensorflow/serving.git
cd serving

Borre el directorio de exportación si ya existe:

rm -rf /tmp/models

Entrene (con 100 iteraciones) y exporte la primera versión del modelo:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=100 --model_version=1 /tmp/mnist

Entrene (con 2000 iteraciones) y exporte la segunda versión del modelo:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=2000 --model_version=2 /tmp/mnist

Como puede ver en mnist_saved_model.py , el entrenamiento y la exportación se realizan de la misma manera que en el tutorial básico de TensorFlow Serving . Para fines de demostración, está reduciendo intencionalmente las iteraciones de entrenamiento para la primera ejecución y exportándolas como v1, mientras las entrena normalmente para la segunda ejecución y las exporta como v2 al mismo directorio principal, como esperamos que logre este último. mejor precisión de clasificación debido a un entrenamiento más intensivo. Deberías ver los datos de entrenamiento para cada ejecución de entrenamiento en tu directorio /tmp/mnist :

$ ls /tmp/mnist
1  2

Núcleo del servidor

Ahora imagine que v1 y v2 del modelo se generan dinámicamente en tiempo de ejecución, a medida que se experimentan con nuevos algoritmos o cuando el modelo se entrena con un nuevo conjunto de datos. En un entorno de producción, es posible que desee crear un servidor que admita una implementación gradual, en el que se pueda descubrir, cargar, experimentar, monitorear o revertir la versión 2 mientras se sirve la versión 1. Alternativamente, es posible que desees derribar la v1 antes de abrir la v2. TensorFlow Serving admite ambas opciones: mientras que una es buena para mantener la disponibilidad durante la transición, la otra es buena para minimizar el uso de recursos (por ejemplo, RAM).

TensorFlow Serving Manager hace exactamente eso. Maneja el ciclo de vida completo de los modelos de TensorFlow, incluida su carga, entrega y descarga, así como las transiciones de versiones. En este tutorial, construirá su servidor sobre TensorFlow Serving ServerCore , que encapsula internamente un AspiredVersionsManager .

int main(int argc, char** argv) {
  ...

  ServerCore::Options options;
  options.model_server_config = model_server_config;
  options.servable_state_monitor_creator = &CreateServableStateMonitor;
  options.custom_model_config_loader = &LoadCustomModelConfig;

  ::google::protobuf::Any source_adapter_config;
  SavedModelBundleSourceAdapterConfig
      saved_model_bundle_source_adapter_config;
  source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
  (*(*options.platform_config_map.mutable_platform_configs())
      [kTensorFlowModelPlatform].mutable_source_adapter_config()) =
      source_adapter_config;

  std::unique_ptr<ServerCore> core;
  TF_CHECK_OK(ServerCore::Create(options, &core));
  RunServer(port, std::move(core));

  return 0;
}

ServerCore::Create() toma un parámetro ServerCore::Options. A continuación se muestran algunas opciones de uso común:

  • ModelServerConfig que especifica los modelos que se cargarán. Los modelos se declaran mediante model_config_list , que declara una lista estática de modelos, o mediante custom_model_config , que define una forma personalizada de declarar una lista de modelos que pueden actualizarse en tiempo de ejecución.
  • PlatformConfigMap que se asigna desde el nombre de la plataforma (como tensorflow ) a PlatformConfig , que se utiliza para crear SourceAdapter . SourceAdapter adapta StoragePath (la ruta donde se descubre una versión del modelo) al Model Loader (carga la versión del modelo desde la ruta de almacenamiento y proporciona interfaces de transición de estado al Manager ). Si PlatformConfig contiene SavedModelBundleSourceAdapterConfig , se creará un SavedModelBundleSourceAdapter , que explicaremos más adelante.

SavedModelBundle es un componente clave de TensorFlow Serving. Representa un modelo de TensorFlow cargado desde una ruta determinada y proporciona la misma interfaz Session::Run que TensorFlow para ejecutar la inferencia. SavedModelBundleSourceAdapter adapta la ruta de almacenamiento a Loader<SavedModelBundle> para que Manager pueda administrar la vida útil del modelo. Tenga en cuenta que SavedModelBundle es el sucesor del obsoleto SessionBundle . Se anima a los usuarios a utilizar SavedModelBundle ya que pronto se eliminará la compatibilidad con SessionBundle .

Con todo esto, ServerCore internamente hace lo siguiente:

  • Crea una instancia de FileSystemStoragePathSource que monitorea las rutas de exportación del modelo declaradas en model_config_list .
  • Crea una instancia de SourceAdapter utilizando PlatformConfigMap con la plataforma modelo declarada en model_config_list y le conecta FileSystemStoragePathSource . De esta manera, cada vez que se descubre una nueva versión del modelo en la ruta de exportación, SavedModelBundleSourceAdapter la adapta a Loader<SavedModelBundle> .
  • Crea una instancia de una implementación específica de Manager llamada AspiredVersionsManager que administra todas las instancias Loader creadas por SavedModelBundleSourceAdapter . ServerCore exporta la interfaz Manager delegando las llamadas a AspiredVersionsManager .

Siempre que hay una nueva versión disponible, este AspiredVersionsManager carga la nueva versión y, según su comportamiento predeterminado, descarga la anterior. Si desea comenzar a personalizar, le recomendamos que comprenda los componentes que crea internamente y cómo configurarlos.

Cabe mencionar que TensorFlow Serving está diseñado desde cero para ser muy flexible y extensible. Puede crear varios complementos para personalizar el comportamiento del sistema y, al mismo tiempo, aprovechar los componentes centrales genéricos como ServerCore y AspiredVersionsManager . Por ejemplo, podría crear un complemento de fuente de datos que supervise el almacenamiento en la nube en lugar del almacenamiento local, o podría crear un complemento de política de versiones que realice la transición de versión de una manera diferente; de ​​hecho, incluso podría crear un complemento de modelo personalizado que sirva Modelos que no son TensorFlow. Estos temas están fuera del alcance de este tutorial. Sin embargo, puede consultar la fuente personalizada y los tutoriales de servicio personalizados para obtener más información.

procesamiento por lotes

Otra característica típica del servidor que queremos en un entorno de producción es el procesamiento por lotes. Los aceleradores de hardware modernos (GPU, etc.) utilizados para realizar inferencias de aprendizaje automático generalmente logran la mejor eficiencia de cálculo cuando las solicitudes de inferencia se ejecutan en lotes grandes.

El procesamiento por lotes se puede activar proporcionando SessionBundleConfig adecuado al crear SavedModelBundleSourceAdapter . En este caso configuramos BatchingParameters con valores prácticamente predeterminados. El procesamiento por lotes se puede ajustar estableciendo valores personalizados de tiempo de espera, tamaño de lote, etc. Para obtener más información, consulte BatchingParameters .

SessionBundleConfig session_bundle_config;
// Batching config
if (enable_batching) {
  BatchingParameters* batching_parameters =
      session_bundle_config.mutable_batching_parameters();
  batching_parameters->mutable_thread_pool_name()->set_value(
      "model_server_batch_threads");
}
*saved_model_bundle_source_adapter_config.mutable_legacy_config() =
    session_bundle_config;

Al alcanzar el lote completo, las solicitudes de inferencia se fusionan internamente en una única solicitud grande (tensor) y se invoca tensorflow::Session::Run() (que es de donde proviene la ganancia de eficiencia real en las GPU).

Servir con el gerente

Como se mencionó anteriormente, TensorFlow Serving Manager está diseñado para ser un componente genérico que puede manejar la carga, entrega, descarga y transición de versiones de modelos generados por sistemas arbitrarios de aprendizaje automático. Sus API se basan en los siguientes conceptos clave:

  • Servable : Servable es cualquier objeto opaco que se puede utilizar para atender solicitudes de clientes. El tamaño y la granularidad de un servable es flexible, de modo que un único servable puede incluir cualquier cosa, desde un único fragmento de una tabla de búsqueda hasta un único modelo aprendido por máquina o una tupla de modelos. Un servable puede ser de cualquier tipo e interfaz.

  • Versión servible : los servables tienen versiones y TensorFlow Serving Manager puede administrar una o más versiones de un servable. El control de versiones permite cargar más de una versión de un servable simultáneamente, lo que permite una implementación y experimentación graduales.

  • Flujo servible : un flujo servible es la secuencia de versiones de un servible, con números de versión crecientes.

  • Modelo : un modelo aprendido por máquina está representado por uno o más servables. Ejemplos de servicios son:

    • Sesión de TensorFlow o envoltorios que las rodean, como SavedModelBundle .
    • Otros tipos de modelos de aprendizaje automático.
    • Tablas de consulta de vocabulario.
    • Incrustar tablas de búsqueda.

    Un modelo compuesto podría representarse como múltiples servables independientes o como un único servible compuesto. Un servable también puede corresponder a una fracción de un modelo, por ejemplo, con una tabla de búsqueda grande dividida en muchas instancias Manager .

Para poner todo esto en el contexto de este tutorial:

  • Los modelos de TensorFlow están representados por un tipo de servable: SavedModelBundle . SavedModelBundle consta internamente de un tensorflow:Session emparejado con algunos metadatos sobre qué gráfico se carga en la sesión y cómo ejecutarlo para inferencia.

  • Hay un directorio del sistema de archivos que contiene un flujo de exportaciones de TensorFlow, cada una en su propio subdirectorio cuyo nombre es un número de versión. Se puede considerar el directorio externo como la representación serializada de la secuencia que se puede servir para el modelo de TensorFlow que se sirve. Cada exportación corresponde a un servable que se puede cargar.

  • AspiredVersionsManager monitorea el flujo de exportación y administra dinámicamente el ciclo de vida de todos los servicios SavedModelBundle .

TensorflowPredictImpl::Predict y luego simplemente:

  • Solicita SavedModelBundle del administrador (a través de ServerCore).
  • Utiliza generic signatures para asignar nombres de tensores lógicos en PredictRequest a nombres de tensores reales y vincular valores a tensores.
  • Ejecuta inferencia.

Probar y ejecutar el servidor

Copie la primera versión de la exportación a la carpeta monitoreada:

mkdir /tmp/monitored
cp -r /tmp/mnist/1 /tmp/monitored

Luego inicie el servidor:

docker run -p 8500:8500 \
  --mount type=bind,source=/tmp/monitored,target=/models/mnist \
  -t --entrypoint=tensorflow_model_server tensorflow/serving --enable_batching \
  --port=8500 --model_name=mnist --model_base_path=/models/mnist &

El servidor emitirá mensajes de registro cada segundo que dicen "Versión aspirante a servable ...", lo que significa que ha encontrado la exportación y está rastreando su existencia continua.

Ejecutemos el cliente con --concurrency=10 . Esto enviará solicitudes simultáneas al servidor y, por lo tanto, activará su lógica de procesamiento por lotes.

tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Lo que da como resultado un resultado similar a:

...
Inference error rate: 13.1%

Luego copiamos la segunda versión de la exportación a la carpeta monitoreada y volvemos a ejecutar la prueba:

cp -r /tmp/mnist/2 /tmp/monitored
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Lo que da como resultado un resultado similar a:

...
Inference error rate: 9.5%

¡Esto confirma que su servidor descubre automáticamente la nueva versión y la utiliza para servir!