Tensorflow se ha convertido en la plataforma estándar para el aprendizaje automático (ML), popular tanto en la industria como en la investigación. Se han creado muchas bibliotecas, herramientas y marcos gratuitos para capacitar y mantener modelos de ML. El proyecto de Tensorflow Serving ayuda a mantener los modelos ML en un entorno de producción distribuido.
Nuestro servicio Mux utiliza Tensorflow Serving en varias partes de la infraestructura, ya hemos discutido el uso de Tensorflow Serving en la codificación de títulos de video. Hoy nos centraremos en métodos que mejoran la latencia optimizando tanto el servidor de pronóstico como el cliente. Los pronósticos de modelos suelen ser operaciones "en línea" (en la ruta crítica de solicitud de una aplicación), por lo tanto, los objetivos principales de la optimización son procesar grandes volúmenes de solicitudes con el menor retraso posible.
¿Qué es el servicio Tensorflow?
Tensorflow Serving proporciona una arquitectura de servidor flexible para implementar y mantener modelos ML. Una vez que el modelo está entrenado y listo para ser utilizado para el pronóstico, Tensorflow Serving requiere exportarlo a un formato compatible (de servicio).
Servable es una abstracción central que envuelve los objetos de Tensorflow. Por ejemplo, un modelo se puede representar como uno o más objetos Servable. Por lo tanto, Servable son los objetos básicos que el cliente usa para realizar cálculos. El tamaño de servicio es importante: los modelos más pequeños ocupan menos espacio, usan menos memoria y se cargan más rápido. Para descargar y mantener utilizando la API de predicción, los modelos deben estar en formato de modelo guardado.

Tensorflow Serving combina los componentes básicos para crear un servidor gRPC / HTTP que sirve a varios modelos ML (o varias versiones), proporciona componentes de monitoreo y una arquitectura personalizada.
Tensorflow Sirviendo con Docker
Echemos un vistazo a las métricas básicas de latencia en el pronóstico del rendimiento con la configuración estándar de Servidor de Tensorflow (sin optimización de CPU).
Primero, descargue la última imagen del centro TensorFlow Docker:
docker pull tensorflow/serving:latest
En este artículo, todos los contenedores se ejecutan en un host con cuatro núcleos, 15 GB, Ubuntu 16.04.
Exportar modelo de Tensorflow a modelo guardado
Cuando un modelo se entrena utilizando Tensorflow, la salida se puede guardar como puntos de control variables (archivos en el disco). La salida se realiza directamente restaurando los puntos de control del modelo o en un formato de gráfico congelado congelado (archivo binario).
Para el servicio Tensorflow, este gráfico congelado debe exportarse al formato GuardadoModelo. La
documentación de Tensorflow contiene ejemplos de exportación de modelos entrenados al formato SavedModel.
Tensorflow también proporciona muchos modelos
oficiales y de investigación como punto de partida para la experimentación, la investigación o la producción.
Como ejemplo, utilizaremos el
modelo de red neuronal residual profunda (ResNet) para clasificar un conjunto de datos ImageNet de 1000 clases. Descargue el modelo
pre -
ResNet-50 v2
, específicamente la
opción Channels_last (NHWC) en
SavedModel : como regla, funciona mejor en la CPU.
Copie el directorio del modelo RestNet en la siguiente estructura:
models/ 1/ saved_model.pb variables/ variables.data-00000-of-00001 variables.index
Tensorflow Serving espera una estructura de directorio ordenada numéricamente para el control de versiones. En nuestro caso, el directorio
1/
corresponde al modelo de la versión 1, que contiene la arquitectura del modelo
saved_model.pb
con una instantánea de los pesos del modelo (variables).
Cargando y procesando el modelo guardado
El siguiente comando inicia el servidor modelo Tensorflow Serving en un contenedor Docker. Para cargar SavedModel, debe montar el directorio del modelo en el directorio del contenedor esperado.
docker run -d -p 9000:8500 \ -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet \ -t tensorflow/serving:latest
La comprobación de los registros del contenedor muestra que ModelServer está en funcionamiento para manejar las solicitudes de salida para el modelo de
resnet
en los puntos finales gRPC y HTTP:
... I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: resnet version: 1} I tensorflow_serving/model_servers/server.cc:286] Running gRPC ModelServer at 0.0.0.0:8500 ... I tensorflow_serving/model_servers/server.cc:302] Exporting HTTP/REST API at:localhost:8501 ...
Cliente de pronóstico
Tensorflow Serving define un esquema de API en formato de
buffers de protocolo (protobufs). Las implementaciones de cliente GRPC para la API de pronóstico se empaquetan como un paquete de Python
tensorflow_serving.apis
. Necesitaremos otro paquete de
tensorflow
de Python para funciones de utilidad.
Instale las dependencias para crear un cliente simple:
virtualenv .env && source .env/bin/activate && \ pip install numpy grpcio opencv-python tensorflow tensorflow-serving-api
El modelo
ResNet-50 v2
espera la entrada de tensores de coma flotante en una estructura de datos formateada channel_last (NHWC). Por lo tanto, la imagen de entrada se lee usando opencv-python y se carga en la matriz numpy (alto × ancho × canales) como un tipo de datos float32. El siguiente script crea un código auxiliar de cliente de predicción y carga los datos JPEG en una matriz numpy, los convierte en tensor_proto para hacer una solicitud de pronóstico para gRPC:
Habiendo recibido una entrada JPEG, un cliente que trabaja producirá el siguiente resultado:
python tf_serving_client.py --image=images/pupper.jpg total time: 2.56152906418s
El tensor resultante contiene un pronóstico en forma de valor entero y probabilidad de signos.
outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 1 } } int64_val: 238 } } outputs { key: "probabilities" ...
Para una sola solicitud, dicho retraso no es aceptable. Pero nada sorprendente: el binario Tensorflow Serving está diseñado por defecto para la gama más amplia de equipos para la mayoría de los casos de uso. Probablemente haya notado las siguientes líneas en los registros del contenedor estándar de Tensorflow Serving:
I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
Esto indica un binario de TensorFlow Serving que se ejecuta en una plataforma de CPU para la que no ha sido optimizado.
Construye un binario optimizado
De acuerdo con la
documentación de Tensorflow, se recomienda compilar Tensorflow desde la fuente con todas las optimizaciones disponibles para la CPU en el host donde funcionará el binario. Al ensamblar, los indicadores especiales permiten la activación de conjuntos de instrucciones de CPU para una plataforma específica:
Clonar un Tensorflow Sirviendo de una versión específica. En nuestro caso, esto es 1.13 (el último en el momento de la publicación de este artículo):
USER=$1 TAG=$2 TF_SERVING_VERSION_GIT_BRANCH="r1.13" git clone --branch="$TF_SERVING_VERSION_GIT_BRANCH" https://github.com/tensorflow/serving
La imagen de desarrollo de Tensorflow Serving utiliza la herramienta de Basilea para construir. Lo configuramos para conjuntos específicos de instrucciones de CPU:
TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2"
Si no hay suficiente memoria, limite el consumo de memoria durante el proceso de compilación con el indicador
--local_resources=2048,.5,1.0
. Para obtener información sobre indicadores, consulte la
ayuda de Tensorflow Serving y Docker , así como la
documentación de Bazel .
Cree una imagen de trabajo basada en la existente:
ModelServer se configura utilizando
indicadores TensorFlow para admitir concurrencia. Las siguientes opciones configuran dos grupos de subprocesos para la operación en paralelo:
intra_op_parallelism_threads
- controla el número máximo de subprocesos para la ejecución paralela de una operación;
- se usa para paralelizar operaciones que tienen suboperaciones que son de naturaleza independiente.
inter_op_parallelism_threads
- controla el número máximo de subprocesos para la ejecución paralela de operaciones independientes;
- Las operaciones de Tensorflow Graph, que son independientes entre sí y, por lo tanto, se pueden realizar en diferentes subprocesos.
Por defecto, ambos parámetros están establecidos en
0
. Esto significa que el sistema mismo selecciona el número apropiado, lo que a menudo significa un subproceso por núcleo. Sin embargo, el parámetro se puede cambiar manualmente para la concurrencia de múltiples núcleos.
Luego, ejecute el contenedor Serving de la misma manera que el anterior, esta vez con una imagen Docker compilada de las fuentes y con indicadores de optimización de Tensorflow para un procesador específico:
docker run -d -p 9000:8500 \ -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet \ -t $USER/tensorflow-serving:$TAG \ --tensorflow_intra_op_parallelism=4 \ --tensorflow_inter_op_parallelism=4
Los registros de contenedores ya no deberían mostrar advertencias sobre una CPU indefinida. Sin cambiar el código en la misma solicitud de pronóstico, el retraso se reduce en aproximadamente un 35,8%:
python tf_serving_client.py --image=images/pupper.jpg total time: 1.64234706879s
Aumente la velocidad en el pronóstico del cliente.
¿Todavía es posible acelerar? Hemos optimizado el lado del servidor para nuestra CPU, pero un retraso de más de 1 segundo todavía parece demasiado grande.
Dio la casualidad de que cargar las bibliotecas
tensorflow_serving
y
tensorflow
hace una contribución significativa al retraso. Cada llamada innecesaria a
tf.contrib.util.make_tensor_proto
también agrega una fracción de segundo.
Puede preguntar: "¿No necesitamos paquetes TensorFlow Python para hacer solicitudes de predicción al servidor Tensorflow?" De hecho, no hay una
necesidad real de paquetes
tensorflow_serving
y
tensorflow
.
Como se señaló anteriormente, las API de predicción de Tensorflow se definen como proto-buffers. Por lo tanto, se pueden reemplazar dos dependencias externas con los
tensorflow
y
tensorflow_serving
correspondientes, y luego no es necesario extraer toda la biblioteca (pesada) de Tensorflow en el cliente.
Primero, elimine las
tensorflow_serving
tensorflow
y
tensorflow_serving
y agregue el paquete
grpcio-tools
.
pip uninstall tensorflow tensorflow-serving-api && \ pip install grpcio-tools==1.0.0
Clone los
tensorflow/tensorflow
y
tensorflow/serving
y copie los siguientes archivos protobuf en el proyecto del cliente:
tensorflow/serving/ tensorflow_serving/apis/model.proto tensorflow_serving/apis/predict.proto tensorflow_serving/apis/prediction_service.proto tensorflow/tensorflow/ tensorflow/core/framework/resource_handle.proto tensorflow/core/framework/tensor_shape.proto tensorflow/core/framework/tensor.proto tensorflow/core/framework/types.proto
Copie estos archivos protobuf en el directorio
protos/
con las rutas originales conservadas:
protos/ tensorflow_serving/ apis/ *.proto tensorflow/ core/ framework/ *.proto
Por simplicidad,
prediction_service.proto puede simplificarse para implementar solo Predict RPC para no descargar las dependencias anidadas de otros RPC especificados en el servicio.
Aquí hay un ejemplo de
prediction_service.
simplificado.
Cree implementaciones de Python gRPC utilizando
grpcio.tools.protoc
:
PROTOC_OUT=protos/ PROTOS=$(find . | grep "\.proto$") for p in $PROTOS; do python -m grpc.tools.protoc -I . --python_out=$PROTOC_OUT --grpc_python_out=$PROTOC_OUT $p done
Ahora se puede eliminar todo el módulo
tensorflow_serving
:
from tensorflow_serving.apis import predict_pb2 from tensorflow_serving.apis import prediction_service_pb2
... y reemplace con los protobuffers generados desde
protos/tensorflow_serving/apis
:
from protos.tensorflow_serving.apis import predict_pb2 from protos.tensorflow_serving.apis import prediction_service_pb2
La biblioteca Tensorflow se importa para usar la función auxiliar
make_tensor_proto
, que es
necesaria para envolver un objeto python / numpy como un objeto TensorProto.
Por lo tanto, podemos reemplazar la siguiente dependencia y fragmento de código:
import tensorflow as tf ... tensor = tf.contrib.util.make_tensor_proto(features) request.inputs['inputs'].CopyFrom(tensor)
importar protobuffers y construir un objeto TensorProto:
from protos.tensorflow.core.framework import tensor_pb2 from protos.tensorflow.core.framework import tensor_shape_pb2 from protos.tensorflow.core.framework import types_pb2 ...
El script completo de Python está
aquí . Ejecute un cliente de inicio actualizado que realice una solicitud de predicción para el servicio optimizado de Tensorflow:
python tf_inception_grpc_client.py --image=images/pupper.jpg total time: 0.58314920859s
El siguiente diagrama muestra el tiempo de ejecución del pronóstico en la versión optimizada de Tensorflow Serving en comparación con el estándar, más de 10 ejecuciones:

El retraso promedio disminuyó en aproximadamente 3.38 veces.
Optimización de ancho de banda
Tensorflow Serving se puede configurar para manejar grandes cantidades de datos. La optimización del ancho de banda generalmente se realiza para el procesamiento por lotes "autónomo", donde los límites de latencia ajustados no son un requisito estricto.
Procesamiento por lotes del lado del servidor
Como se indica en la
documentación , el procesamiento por lotes del lado del servidor es compatible de forma nativa en Tensorflow Serving.
Las compensaciones entre latencia y rendimiento se determinan mediante parámetros de procesamiento por lotes. Le permiten alcanzar el rendimiento máximo que los aceleradores de hardware son capaces de hacer.
Para habilitar el empaquetado, configure los
--batching_parameters_file
--enable_batching
y
--batching_parameters_file
. Los parámetros se establecen de acuerdo con
SessionBundleConfig . Para sistemas en la CPU, establezca
num_batch_threads
en el número de núcleos disponibles. Para la GPU, vea los parámetros apropiados
aquí .
Después de completar todo el paquete en el lado del servidor, las solicitudes de emisión se combinan en una solicitud grande (tensor) y se envían a la sesión de Tensorflow con una solicitud combinada. En esta situación, el paralelismo CPU / GPU está realmente involucrado.
Algunos usos comunes para el procesamiento por lotes de Tensorflow:
- Uso de solicitudes de cliente asíncronas para llenar paquetes del lado del servidor
- Procesamiento por lotes más rápido mediante la transferencia de los componentes del gráfico del modelo a la CPU / GPU
- Sirviendo solicitudes de múltiples modelos desde un único servidor
- El procesamiento por lotes es muy recomendable para el procesamiento "fuera de línea" de una gran cantidad de solicitudes
Procesamiento por lotes del lado del cliente
El procesamiento por lotes del lado del cliente agrupa varias solicitudes entrantes en una.
Dado que el modelo ResNet está esperando entrada en formato NHWC (la primera dimensión es la cantidad de entradas), podemos combinar varias imágenes de entrada en una solicitud RPC:
... batch = [] for jpeg in os.listdir(FLAGS.images_path): path = os.path.join(FLAGS.images_path, jpeg) img = cv2.imread(path).astype(np.float32) batch.append(img) ... batch_np = np.array(batch).astype(np.float32) dims = [tensor_shape_pb2.TensorShapeProto.Dim(size=dim) for dim in batch_np.shape] t_shape = tensor_shape_pb2.TensorShapeProto(dim=dims) tensor = tensor_pb2.TensorProto( dtype=types_pb2.DT_FLOAT, tensor_shape=t_shape, float_val=list(batched_np.reshape(-1))) request.inputs['inputs'].CopyFrom(tensor)
Para un paquete de N imágenes, el tensor de salida en la respuesta contendrá los resultados de predicción para el mismo número de entradas. En nuestro caso, N = 2:
outputs { key: "classes" value { dtype: DT_INT64 tensor_shape { dim { size: 2 } } int64_val: 238 int64_val: 121 } } ...
Aceleración de hardware
Algunas palabras sobre las GPU.
El proceso de aprendizaje utiliza naturalmente la paralelización en la GPU, ya que la construcción de redes neuronales profundas requiere cálculos masivos para lograr la solución óptima.
Pero para generar resultados, la paralelización no es tan obvia. A menudo, puede acelerar la salida de una red neuronal a una GPU, pero debe seleccionar y probar cuidadosamente el equipo y realizar un análisis técnico y económico en profundidad. La paralelización de hardware es más valiosa para el procesamiento por lotes de conclusiones "autónomas" (volúmenes masivos).
Antes de pasar a una GPU, considere los requisitos comerciales con un análisis cuidadoso de los costos (monetario, operativo, técnico) para obtener el mayor beneficio (latencia reducida, alto rendimiento).