Logotipo de Zephyrnet

Sirva varios modelos con Amazon SageMaker y Triton Inference Server

Fecha:

Amazon SageMaker es un servicio completamente administrado para flujos de trabajo de ciencia de datos y aprendizaje automático (ML). Ayuda a los científicos y desarrolladores de datos a preparar, construir, entrenar e implementar modelos ML de alta calidad rápidamente al reunir un amplio conjunto de capacidades especialmente diseñadas para ML.

En 2021, AWS anunció la integración de Servidor de inferencia NVIDIA Triton en SageMaker. Puede usar NVIDIA Triton Inference Server para servir modelos para inferencia en SageMaker. Al usar una imagen de contenedor de NVIDIA Triton, puede servir fácilmente modelos de ML y beneficiarse de las optimizaciones de rendimiento, el procesamiento por lotes dinámico y la compatibilidad con varios marcos que proporciona NVIDIA Triton. Triton ayuda a maximizar la utilización de GPU y CPU, lo que reduce aún más el costo de la inferencia.

En algunos escenarios, los usuarios desean implementar varios modelos. Por ejemplo, una aplicación para revisar la composición en inglés siempre incluye varios modelos, como BERT para la clasificación de textos y GECToR para la revisión gramatical. Una solicitud típica puede fluir a través de múltiples modelos, como preprocesamiento de datos, BERT, GECToR y posprocesamiento, y se ejecutan en serie como canalizaciones de inferencia. Si estos modelos se alojan en diferentes instancias, la latencia de red adicional entre estas instancias aumenta la latencia general. Para una aplicación con tráfico incierto, la implementación de múltiples modelos en diferentes instancias conducirá inevitablemente a una utilización ineficiente de los recursos.

Considere otro escenario, en el que los usuarios desarrollan varios modelos con diferentes versiones, y cada modelo usa un marco de entrenamiento diferente. Una práctica común es usar varios contenedores, cada uno de los cuales implementa un modelo. Pero esto aumentará la carga de trabajo y los costos de desarrollo, operación y mantenimiento. En esta publicación, analizamos cómo SageMaker y NVIDIA Triton Inference Server pueden resolver este problema.

Resumen de la solución

Veamos cómo funciona la inferencia de SageMaker. SageMaker invoca el servicio de hospedaje ejecutando un contenedor Docker. El contenedor Docker lanza un servidor de inferencia RESTful (como Flask) para servir solicitudes HTTP para inferencia. El servidor de inferencia carga el modelo y escucha el puerto 8080 proporcionando un servicio externo. La aplicación cliente envía una solicitud POST al extremo de SageMaker, SageMaker pasa la solicitud al contenedor y devuelve el resultado de la inferencia del contenedor al cliente.

En nuestra arquitectura, usamos NVIDIA Triton Inference Server, que proporciona ejecuciones simultáneas de varios modelos de diferentes marcos, y usamos un servidor Flask para procesar solicitudes del lado del cliente y enviar estas solicitudes al servidor Triton backend. Al iniciar un contenedor Docker, el servidor Triton y el servidor Flask se inician automáticamente. El servidor Triton carga varios modelos y expone los puertos 8000, 8001 y 8002 como servidor gRPC, HTTP y de métricas. El servidor Flask escucha los puertos 8080 y analiza la solicitud original y la carga útil, y luego invoca el backend local de Triton a través del nombre del modelo y la información de la versión. Para el lado del cliente, agrega el nombre del modelo y la versión del modelo en la solicitud además de la carga útil original, de modo que Flask pueda enrutar la solicitud de inferencia al modelo correcto en el servidor Triton.

El siguiente diagrama ilustra este proceso.

Arquitectura de soluciones

Una llamada API completa del cliente es la siguiente:

  1. El cliente ensambla la solicitud e inicia la solicitud a un extremo de SageMaker.
  2. El servidor Flask recibe y analiza la solicitud y obtiene el nombre del modelo, la versión y la carga útil.
  3. El servidor Flask vuelve a ensamblar la solicitud y la enruta al punto final correspondiente del servidor Triton según el nombre del modelo y la versión.
  4. El servidor Triton ejecuta una solicitud de inferencia y envía respuestas al servidor Flask.
  5. El servidor Flask recibe el mensaje de respuesta, ensambla el mensaje nuevamente y lo devuelve al cliente.
  6. El cliente recibe y analiza la respuesta y continúa con los procedimientos comerciales posteriores.

En las siguientes secciones, presentamos los pasos necesarios para preparar un modelo y construir el motor TensorRT, preparar una imagen de Docker, crear un extremo de SageMaker y verificar el resultado.

Prepara modelos y construye el motor.

Demostramos el alojamiento de tres modelos típicos de ML en nuestra solución: clasificación de imágenes (ResNet50), detección de objetos (YOLOv5) y un modelo de procesamiento de lenguaje natural (NLP) (base BERT). NVIDIA Triton Inference Server admite varios formatos, incluidos TensorFlow 1. x y 2. x, TensorFlow SavedModel, TensorFlow GraphDef, TensorRT, ONNX, OpenVINO y PyTorch TorchScript.

La siguiente tabla resume los detalles de nuestro modelo.

Nombre de Modelo Tamaño modelo Formato
ResNet50 52 m Tensor RT
YOLOv5 38 m Tensor RT
base BERT 133 m ONNX-RT

NVIDIA proporciona información detallada documentación describiendo cómo generar el motor TensorRT. Para lograr el mejor rendimiento, el motor TensorRT debe construirse sobre el dispositivo. Esto significa que el tiempo de compilación y el tiempo de ejecución requieren la misma capacidad informática. Por ejemplo, un motor TensorRT creado en una instancia g4dn no se puede implementar en una instancia g5.

Puedes generar tus propios motores TensorRT según tus necesidades. Para fines de prueba, preparamos códigos de muestra y modelos desplegables con el motor TensorRT. El código fuente también está disponible en GitHub.

A continuación, usamos un Nube informática elástica de Amazon (Amazon EC2) Instancia G4dn para generar el motor TensorRT con los siguientes pasos. Usamos YOLOv5 como ejemplo.

  1. Inicie una instancia EC4 G2dn.2xlarge con Deep Learning AMI (Ubuntu 20.04) en el us-east-1 Región.
  2. Abra una ventana de terminal y use el ssh comando para conectarse a la instancia.
  3. Ejecute los siguientes comandos uno por uno:
    nvidia-docker run --gpus all -it --rm -v `pwd`/workspace:/workspace nvcr.io/nvidia/pytorch:22.04-py3
    git clone -b v7.0.1 https://github.com/ultralytics/yolov5
    pip install seaborn
    pip install onnx-simplifier
    cd yolov5
    wget https://github.com/ultralytics/yolov5/releases/download/v6.2/yolov5s.pt
    python export.py --weights yolov5s.pt --include onnx --simplify --imgsz 640 640 --device 0
    onnxsim yolov5s.onnx yolov5s-sim.onnx trtexec --onnx=yolov5s-sim.onnx --saveEngine=model.plan --explicitBatch --workspace=1024*12
    

  4. Créar un config.pbtxt archivo:
    name: "yolov5s"
    platform: "tensorrt_plan"
    input: [ { name: "images" data_type: TYPE_FP32 format: FORMAT_NONE dims: [1, 3, 640, 640 ] }
    ]
    output: [ { name: "output", data_type: TYPE_FP32 dims: [1,25200,85 ] }
    ]
    

  5. Cree la siguiente estructura de archivos y coloque los archivos generados en la ubicación adecuada:
    mkdir yolov5s
    mkdir -p yolov5s/1
    cp config.pbtxt yolov5s
    cp model.plan yolov5s/1 yolov5s
    ├── 1
    │   └── model.plan
    └── config.pbtxt

Probar el motor TensorRT

Antes de implementar SageMaker, iniciamos un servidor Triton para verificar que estos tres modelos estén configurados correctamente. Utilice el siguiente comando para iniciar un servidor Triton y cargar los modelos:

docker run --gpus all --rm -p8000:8000 -p8001:8001 -v<MODEL_ROOT_DIR>/model_repository:/models nvcr.io/nvidia/tritonserver:22.04-py3 tritonserver --model-repository=/models

Si recibe el siguiente mensaje de aviso, significa que el servidor Triton se inició correctamente.

Participar nvidia-smi en la terminal para ver el uso de la memoria GPU.

Implementación de cliente para inferencia

La estructura del archivo es la siguiente:

  • ayudar – El contenedor que inicia el servidor de inferencia. El script de Python inicia el servidor NGINX, Flask y Triton.
  • predictor.py – La implementación Flask para /ping y /invocations puntos finales y solicitudes de despacho.
  • wsgi.py – El shell de inicio para los trabajadores del servidor individuales.
  • base.py – La definición del método abstracto que requiere cada cliente para implementar su método de inferencia.
  • carpeta del cliente – Una carpeta por cliente:
    • resnet
    • bert_base
    • yolov5
  • nginx.conf – La configuración para el servidor primario NGINX.

Definimos un método abstracto para implementar la interfaz de inferencia, y cada cliente implementa este método:

from abc import ABC, abstractmethod
class Base(ABC): @abstractmethod def inference(self,img): pass

El servidor Triton expone un extremo HTTP en el puerto 8000, un extremo gRPC en el puerto 8001 y un extremo de métricas de Prometheus en el puerto 8002. El siguiente es un ejemplo de cliente ResNet con una llamada gRPC. Puede implementar la interfaz HTTP o la interfaz gRPC según su caso de uso.

from base import Base
import numpy as np
import tritonclient.grpc as grpcclient
from PIL import Image
import cv2
class Resnet(Base): def image_transform_onnx(self, image, size: int) -> np.ndarray: '''Image transform helper for onnx runtime inference.''' img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) #OpenCV follows BGR convention and PIL follows RGB image = Image.fromarray(img) image = image.resize((size,size)) # now our image is represented by 3 layers - Red, Green, Blue # each layer has a 224 x 224 values representing image = np.array(image) # dummy input for the model at export - torch.randn(1, 3, 224, 224) image = image.transpose(2,0,1).astype(np.float32) # our image is currently represented by values ranging between 0-255 # we need to convert these values to 0.0-1.0 - those are the values that are expected by our model image /= 255 image = image[None, ...] return image def inference(self, img): INPUT_SHAPE = (224, 224) TRITON_IP = "localhost" TRITON_PORT = 8001 MODEL_NAME = "resnet" INPUTS = [] OUTPUTS = [] INPUT_LAYER_NAME = "input" OUTPUT_LAYER_NAME = "output" INPUTS.append(grpcclient.InferInput(INPUT_LAYER_NAME, [1, 3, INPUT_SHAPE[0], INPUT_SHAPE[1]], "FP32")) OUTPUTS.append(grpcclient.InferRequestedOutput(OUTPUT_LAYER_NAME, class_count=3)) TRITON_CLIENT = grpcclient.InferenceServerClient(url=f"{TRITON_IP}:{TRITON_PORT}") INPUTS[0].set_data_from_numpy(self.image_transform_onnx(img, 224)) results = TRITON_CLIENT.infer(model_name=MODEL_NAME, inputs=INPUTS, outputs=OUTPUTS, headers={}) output = np.squeeze(results.as_numpy(OUTPUT_LAYER_NAME)) #print(output) lista = [x.decode('utf-8') for x in output.tolist()] return lista

En esta arquitectura, los servidores NGINX, Flask y Triton deben iniciarse desde el principio. Editar el ayudar archivo y agregue una línea para iniciar el servidor Triton.

Cree una imagen de Docker y envíe la imagen a Amazon ECR

El código del archivo Docker tiene el siguiente aspecto:

FROM nvcr.io/nvidia/tritonserver:22.04-py3 # Add arguments to achieve the version, python and url
ARG PYTHON=python3
ARG PYTHON_PIP=python3-pip
ARG PIP=pip3 ENV LANG=C.UTF-8 RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A4B469963BF863CC && apt-get update && apt-get install -y nginx && apt-get install -y libgl1-mesa-glx && apt-get clean && rm -rf /var/lib/apt/lists/* RUN ${PIP} install -U --no-cache-dir tritonclient[all] torch torchvision pillow==9.1.1 scipy==1.8.1 transformers==4.20.1 opencv-python==4.6.0.66 flask gunicorn && ldconfig && apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/* /tmp/* ~/* &&
mkdir -p /opt/program/models/ COPY sm /opt/program
COPY model /opt/program/models
WORKDIR /opt/program ENTRYPOINT ["python3", "serve"]

Instalar y configurar el aws-cli cliente con el siguiente código:

sudo apt install awscli
sudo apt install git-all
aws configure
# # input AWS Access Key ID, AWS Secret Access Key, Default region name and Default output format

Ejecute el siguiente comando para crear la imagen de Docker y enviar la imagen a Registro de contenedores elásticos de Amazon (Amazon ECR). Proporcione su región e ID de cuenta.

aws ecr get-login-password --region <regionID> | docker login --username AWS --password-stdin <accountID>.dkr.ecr.<regionID>.amazonaws.com docker build -t inference/mytriton . docker tag inference/mytriton:latest <accountID>.dkr.ecr. <regionID>.amazonaws.com/inference/mytriton:latest docker push <accountID>.dkr.ecr.<regionID>.amazonaws.com/inference/mytriton:latest

Crear un punto final de SageMaker y probar el punto final

Ahora es el momento de verificar el resultado. Inicie una instancia de libreta con una instancia ml.c5.xlarge desde la consola de SageMaker y cree una libreta con la conda_python3 núcleo. El siguiente fragmento de código muestra un ejemplo de implementación de un punto final de inferencia. El código fuente está disponible en el Repositorio GitHub.

role = get_execution_role()
sess = sage.Session()
account = sess.boto_session.client('sts').get_caller_identity()['Account']
region = sess.boto_session.region_name
image = '{}.dkr.ecr.{}.amazonaws.com/inference/mytriton:latest'.format(account, region)
model = sess.create_model( name="mytriton", role=role, container_defs=image)
endpoint_cfg=sess.create_endpoint_config( name="MYTRITONCFG", model_name="mytriton", initial_instance_count=1, instance_type="ml.g4dn.xlarge" )
endpoint=sess.create_endpoint( endpoint_name="MyTritonEndpoint", config_name="MYTRITONCFG")

Espere unos 3 minutos hasta que se inicie el servidor de inferencia para verificar el resultado.

El siguiente código es la solicitud del cliente ResNet:

## resnet client
runtime = boto3.Session().client('runtime.sagemaker')
img = cv2.imread('dog.jpg')
string_img = base64.b64encode(cv2.imencode('.jpg', img)[1]).decode()
payload = json.dumps({"modelname": "resnet","payload": {"img":string_img}}) endpoint="MyTritonEndpoint"
response = runtime.invoke_endpoint(EndpointName=endpoint,ContentType="application/json",Body=payload,Accept='application/json') out=response['Body'].read()
res=eval(out)
print(res)

Obtenemos la siguiente respuesta:

{'modelname': 'resnet', 'result': ['11.250000:250:250:malamute, malemute, Alaskan malamute', '9.914062:249:249:Eskimo dog, husky', '9.906250:248:248:Saint Bernard, St Bernard']}

El siguiente código es la solicitud del cliente YOLOv5:

# yolov5 client
payload = json.dumps({"modelname": "yolov5","payload": {"img":string_img}}) endpoint="MyTritonEndpoint"
response = runtime.invoke_endpoint(EndpointName=endpoint,ContentType="application/json",Body=payload,Accept='application/json') out=response['Body'].read()
res=eval(out)
print(str(out))

Obtenemos la siguiente respuesta:

b'{"modelname": "yolov5", "result": [[16, 0.9168673157691956, 111.92530059814453, 258.53240966796875, 262.0159606933594, 533.407958984375, 768, 576], [2, 0.6941519379615784, 392.20037841796875, 573.6005249023438, 142.55178833007812, 224.56454467773438, 768, 576], [1, 0.5813695788383484, 131.8942413330078, 473.7420654296875, 179.61459350585938, 427.0913391113281, 768, 576], [7, 0.5316226482391357, 392.82275390625, 572.4647216796875, 144.685546875, 223.052734375, 768, 576]]}'

El siguiente código es la solicitud del cliente BERT:

# bert client
text="The world has [MASK] people." payload = json.dumps({"modelname": "bert_base","payload": {"text":text}}) endpoint="MyTritonEndpoint"
response = runtime.invoke_endpoint(EndpointName=endpoint,ContentType="application/json",Body=payload,Accept='application/json') out=response['Body'].read()
res=eval(out)
print(res)

Obtenemos la siguiente respuesta:

{'modelname': 'bert_base', 'result': [{'token': 'The world has many people.', 'score': 0.16609132289886475}, {'token': 'The world has no people.', 'score': 0.07334889471530914}, {'token': 'The world has few people.', 'score': 0.0617995485663414}, {'token': 'The world has two people.', 'score': 0.03924647718667984}, {'token': 'The world has its people.', 'score': 0.023465465754270554}]}

Aquí vemos que nuestra arquitectura funciona como se esperaba.

Tenga en cuenta que el hospedaje de un punto final generará algunos costos. Por lo tanto, elimine el punto final después de completar la prueba:

runtime.delete_endpoint(EndpointName=endpoint)

Estimación de costos

Para estimar el costo, suponga que tiene tres modelos, pero no todos son de larga duración. Está utilizando un punto final para cada modelo y el tiempo en línea de cada punto final es diferente. Usando ml.g4dn.xlarge como ejemplo, el costo total es de aproximadamente $971.52/mes. La siguiente tabla enumera los detalles.

Nombre de Modelo Punto final en ejecución/día Tipo de instancia Costo/mes (us-east-1)
Resnet 24 horas ml.g4dn.xgrande 0.736 * 24 * 30=$529.92
BERTI 8 horas ml.g4dn.xgrande 0.736 * 8 * 30=$176.64
YOLOv5 12 horas ml.g4dn.xgrande 0.736 * 12 * 30=$264.96

La siguiente tabla muestra el costo de compartir un punto final para tres modelos que utilizan la arquitectura anterior. El costo total es de aproximadamente $676.8/mes. A partir de este resultado, podemos concluir que puede ahorrar un 30 % en costos y al mismo tiempo tener un servicio 24/7 desde su terminal.

Nombre de Modelo Punto final en ejecución/día Tipo de instancia Costo/mes (us-east-1)
ResNet, YOLOv5, BERT 24 horas ml.g4dn.2xgrande 0.94 * 24 * 30 = 676.8 dólares

Resumen

En esta publicación, presentamos una arquitectura mejorada en la que varios modelos comparten un punto final en SageMaker. Bajo algunas condiciones, esta solución puede ayudarlo a ahorrar costos y mejorar la utilización de recursos. Es adecuado para escenarios empresariales con baja simultaneidad y requisitos insensibles a la latencia.

Para obtener más información sobre SageMaker y las soluciones AI/ML, consulte Amazon SageMaker.

Referencias


Sobre los autores

Zheng Zhang es un arquitecto de soluciones especializado sénior en AWS, se enfoca en ayudar a los clientes a acelerar el entrenamiento, la inferencia y la implementación de modelos para soluciones de aprendizaje automático. También tiene una rica experiencia en capacitación distribuida a gran escala, diseño de soluciones AI/ML.

Yinuo él es especialista en AI/ML en AWS. Tiene experiencia en el diseño y desarrollo de productos basados ​​en aprendizaje automático para brindar mejores experiencias de usuario. Ahora trabaja para ayudar a los clientes a tener éxito en su proceso de aprendizaje automático.

punto_img

Información más reciente

punto_img