Práticas recomendadas para teste de carga endpoints de inferência em tempo real do Amazon SageMaker

Práticas recomendadas para teste de carga endpoints de inferência em tempo real do Amazon SageMaker

Nó Fonte: 1889926

Amazon Sage Maker é um serviço de aprendizado de máquina (ML) totalmente gerenciado. Com o SageMaker, os cientistas e desenvolvedores de dados podem criar e treinar modelos de ML de forma rápida e fácil e, em seguida, implantá-los diretamente em um ambiente hospedado pronto para produção. Ele fornece uma instância integrada de bloco de anotações de autoria do Jupyter para fácil acesso às suas fontes de dados para exploração e análise, para que você não precise gerenciar servidores. Ele também fornece recursos comuns Algoritmos de ML que são otimizados para serem executados com eficiência em dados extremamente grandes em um ambiente distribuído.

A inferência em tempo real do SageMaker é ideal para cargas de trabalho com requisitos de baixa latência, interativos e em tempo real. Com a inferência em tempo real do SageMaker, você pode implantar endpoints REST apoiados por um tipo de instância específico com uma certa quantidade de computação e memória. A implantação de um endpoint em tempo real do SageMaker é apenas a primeira etapa no caminho para a produção para muitos clientes. Queremos ser capazes de maximizar o desempenho do endpoint para atingir uma meta de transações por segundo (TPS) ao mesmo tempo em que aderimos aos requisitos de latência. Uma grande parte da otimização de desempenho para inferência é garantir que você selecione o tipo de instância adequado e conte até um endpoint.

Esta postagem descreve as práticas recomendadas para o teste de carga de um endpoint do SageMaker para encontrar a configuração correta para o número de instâncias e tamanho. Isso pode nos ajudar a entender os requisitos mínimos de instância provisionada para atender aos nossos requisitos de latência e TPS. A partir daí, nos aprofundamos em como você pode rastrear e entender as métricas e o desempenho do terminal SageMaker utilizando Amazon CloudWatch Métricas.

Primeiro, comparamos o desempenho de nosso modelo em uma única instância para identificar o TPS que ele pode manipular de acordo com nossos requisitos de latência aceitáveis. Em seguida, extrapolamos as descobertas para decidir sobre o número de instâncias necessárias para lidar com nosso tráfego de produção. Por fim, simulamos o tráfego em nível de produção e configuramos testes de carga para um endpoint SageMaker em tempo real para confirmar que nosso endpoint pode lidar com a carga em nível de produção. Todo o conjunto de código para o exemplo está disponível no seguinte Repositório GitHub.

Visão geral da solução

Para esta postagem, implantamos um pré-treinado Modelo rosto abraçado DistilBERT do Abraçando o Face Hub. Esse modelo pode executar várias tarefas, mas enviamos uma carga útil especificamente para análise de sentimento e classificação de texto. Com esta carga útil de amostra, nos esforçamos para atingir 1000 TPS.

Implante um endpoint em tempo real

Esta postagem pressupõe que você esteja familiarizado com a implantação de um modelo. Referir-se Crie seu endpoint e implante seu modelo para entender os aspectos internos por trás da hospedagem de um endpoint. Por enquanto, podemos apontar rapidamente para esse modelo no Hugging Face Hub e implantar um endpoint em tempo real com o seguinte trecho de código:

# Hub Model configuration. https://huggingface.co/models
hub = { 'HF_MODEL_ID':'distilbert-base-uncased', 'HF_TASK':'text-classification'
} # create Hugging Face Model Class
huggingface_model = HuggingFaceModel(
transformers_version='4.17.0',
pytorch_version='1.10.2',
py_version='py38',
env=hub,
role=role,
) # deploy model to SageMaker Inference
predictor = huggingface_model.deploy(
initial_instance_count=1, # number of instances
instance_type='ml.m5.12xlarge' # ec2 instance type
)

Vamos testar nosso endpoint rapidamente com o payload de amostra que queremos usar para o teste de carga:


import boto3
import json
client = boto3.client('sagemaker-runtime')
content_type = "application/json"
request_body = {'inputs': "I am super happy right now."}
data = json.loads(json.dumps(request_body))
payload = json.dumps(data)
response = client.invoke_endpoint(
EndpointName=predictor.endpoint_name,
ContentType=content_type,
Body=payload)
result = response['Body'].read()
result

Observe que estamos apoiando o endpoint usando um único Amazon Elastic Compute Nuvem (Amazon EC2) instância do tipo ml.m5.12xlarge, que contém 48 vCPU e 192 GiB de memória. O número de vCPUs é uma boa indicação da simultaneidade com a qual a instância pode lidar. Em geral, é recomendável testar diferentes tipos de instância para garantir que temos uma instância com recursos utilizados corretamente. Para ver uma lista completa de instâncias do SageMaker e seu poder de computação correspondente para inferência em tempo real, consulte Preços do Amazon SageMaker.

Métricas para rastrear

Antes de entrarmos no teste de carga, é essencial entender quais métricas rastrear para entender a divisão de desempenho do endpoint do SageMaker. O CloudWatch é a principal ferramenta de registro que o SageMaker usa para ajudá-lo a entender as diferentes métricas que descrevem o desempenho do seu endpoint. Você pode utilizar os logs do CloudWatch para depurar suas invocações de endpoint; todas as declarações de registro e impressão que você tem em seu código de inferência são capturadas aqui. Para mais informações, consulte Como funciona o Amazon CloudWatch.

Há dois tipos diferentes de métricas que o CloudWatch abrange para o SageMaker: métricas em nível de instância e de invocação.

Métricas no nível da instância

O primeiro conjunto de parâmetros a considerar são as métricas no nível da instância: CPUUtilization e MemoryUtilization (para instâncias baseadas em GPU, GPUUtilization). Para CPUUtilization, você pode ver porcentagens acima de 100% no início no CloudWatch. É importante perceber para CPUUtilization, a soma de todos os núcleos da CPU está sendo exibida. Por exemplo, se a instância por trás do endpoint contiver 4 vCPUs, isso significa que o intervalo de utilização é de até 400%. MemoryUtilization, por outro lado, está na faixa de 0 a 100%.

Especificamente, você pode usar CPUUtilization para entender melhor se você tem hardware suficiente ou mesmo em excesso. Se você tiver uma instância subutilizada (menos de 30%), poderá reduzir seu tipo de instância. Por outro lado, se você estiver em torno de 80 a 90% de utilização, seria benéfico escolher uma instância com maior computação/memória. De nossos testes, sugerimos cerca de 60 a 70% de utilização do seu hardware.

Métricas de invocação

Conforme sugerido pelo nome, as métricas de invocação são onde podemos rastrear a latência de ponta a ponta de qualquer invocação para seu endpoint. Você pode utilizar as métricas de invocação para capturar contagens de erros e que tipo de erros (5xx, 4xx e assim por diante) seu endpoint pode estar enfrentando. Mais importante, você pode entender a quebra de latência de suas chamadas de terminal. Muito disso pode ser capturado com ModelLatency e OverheadLatency métricas, conforme ilustrado no diagrama a seguir.

Latências

A ModelLatency A métrica captura o tempo que a inferência leva dentro do contêiner do modelo atrás de um endpoint do SageMaker. Observe que o contêiner de modelo também inclui qualquer código de inferência personalizado ou scripts que você passou para inferência. Essa unidade é capturada em microssegundos como uma métrica de invocação e, geralmente, você pode representar graficamente um percentil no CloudWatch (p99, p90 e assim por diante) para ver se está atingindo a latência desejada. Observe que vários fatores podem afetar a latência do modelo e do contêiner, como os seguintes:

  • Script de inferência personalizado – Se você implementou seu próprio contêiner ou usou um contêiner baseado no SageMaker com manipuladores de inferência personalizados, é uma prática recomendada criar o perfil de seu script para capturar todas as operações que estão especificamente adicionando muito tempo à sua latência.
  • Protocolo de comunicação – Considere as conexões REST vs. gRPC para o servidor de modelo dentro do contêiner de modelo.
  • Otimizações da estrutura do modelo – Isso é específico da estrutura, por exemplo, com TensorFlow, há várias variáveis ​​de ambiente que você pode ajustar que são específicas do TF Serving. Certifique-se de verificar qual contêiner você está usando e se há otimizações específicas da estrutura que você pode adicionar no script ou como variáveis ​​de ambiente para injetar no contêiner.

OverheadLatency é medido desde o momento em que o SageMaker recebe a solicitação até retornar uma resposta ao cliente, menos a latência do modelo. Esta parte está em grande parte fora de seu controle e se enquadra no tempo gasto pelas despesas gerais do SageMaker.

A latência de ponta a ponta como um todo depende de uma variedade de fatores e não é necessariamente a soma de ModelLatency mais OverheadLatency. Por exemplo, se o seu cliente está fazendo o InvokeEndpoint Chamada de API pela internet, do ponto de vista do cliente, a latência de ponta a ponta seria internet + ModelLatency + OverheadLatency. Dessa forma, ao carregar o teste de seu endpoint para comparar com precisão o próprio endpoint, é recomendável focar nas métricas do endpoint (ModelLatency, OverheadLatency e InvocationsPerInstance) para comparar com precisão o endpoint do SageMaker. Quaisquer problemas relacionados à latência de ponta a ponta podem ser isolados separadamente.

Algumas perguntas a serem consideradas para latência de ponta a ponta:

  • Onde está o cliente que está chamando seu endpoint?
  • Existem camadas intermediárias entre seu cliente e o tempo de execução do SageMaker?

Escala automática

Não abordamos o dimensionamento automático nesta postagem especificamente, mas é uma consideração importante para provisionar o número correto de instâncias com base na carga de trabalho. Dependendo de seus padrões de tráfego, você pode anexar um política de escala automática para o ponto de extremidade do SageMaker. Existem diferentes opções de escala, como TargetTrackingScaling, SimpleScaling e StepScaling. Isso permite que seu endpoint aumente e diminua automaticamente com base em seu padrão de tráfego.

Uma opção comum é o rastreamento de destino, onde você pode especificar uma métrica do CloudWatch ou uma métrica personalizada que definiu e dimensionar com base nisso. Uma utilização frequente do dimensionamento automático é rastrear o InvocationsPerInstance métrica. Depois de identificar um gargalo em um determinado TPS, geralmente você pode usá-lo como uma métrica para escalar para um número maior de instâncias para poder lidar com picos de tráfego. Para obter uma análise mais detalhada dos endpoints do SageMaker de dimensionamento automático, consulte Configurar endpoints de inferência de escalonamento automático no Amazon SageMaker.

Teste de carga

Embora utilizemos o Locust para mostrar como podemos carregar o teste em escala, se você estiver tentando dimensionar corretamente a instância atrás do endpoint, Recomendador de inferência do SageMaker é uma opção mais eficiente. Com ferramentas de teste de carga de terceiros, você precisa implantar endpoints manualmente em diferentes instâncias. Com o Inference Recommender, você pode simplesmente passar uma matriz dos tipos de instância com os quais deseja carregar o teste, e o SageMaker será iniciado empregos para cada uma dessas instâncias.

Gafanhoto

Para este exemplo, usamos Gafanhoto, uma ferramenta de teste de carga de código aberto que você pode implementar usando Python. O Locust é semelhante a muitas outras ferramentas de teste de carga de código aberto, mas tem alguns benefícios específicos:

  • Fácil de configurar – Como demonstramos nesta postagem, passaremos um script Python simples que pode ser facilmente refatorado para seu endpoint e carga útil específicos.
  • Distribuído e escalável – Locust é baseado em eventos e utiliza evento sob o capô. Isso é muito útil para testar cargas de trabalho altamente simultâneas e simular milhares de usuários simultâneos. Você pode obter um alto TPS com um único processo executando o Locust, mas ele também tem um geração de carga distribuída recurso que permite escalar para vários processos e máquinas clientes, conforme exploraremos nesta postagem.
  • Métricas e IU do Locust – O Locust também captura a latência de ponta a ponta como uma métrica. Isso pode ajudar a complementar suas métricas do CloudWatch para pintar uma imagem completa de seus testes. Tudo isso é capturado na IU do Locust, onde você pode rastrear usuários simultâneos, funcionários e muito mais.

Para entender melhor o Locust, confira o documentação.

Configuração do Amazon EC2

Você pode configurar o Locust em qualquer ambiente compatível para você. Para este post, configuramos uma instância do EC2 e instalamos o Locust nela para conduzir nossos testes. Usamos uma instância EC5.18 c2xlarge. O poder de computação do lado do cliente também é algo a ser considerado. Às vezes, quando você fica sem poder de computação no lado do cliente, isso geralmente não é capturado e é confundido como um erro de endpoint do SageMaker. É importante colocar seu cliente em um local com capacidade de computação suficiente para lidar com a carga na qual você está testando. Para nossa instância do EC2, usamos uma AMI de aprendizado profundo do Ubuntu, mas você pode utilizar qualquer AMI, desde que possa configurar o Locust corretamente na máquina. Para entender como iniciar e conectar-se à sua instância do EC2, consulte o tutorial Comece a usar as instâncias Linux do Amazon EC2.

A IU do Locust pode ser acessada por meio da porta 8089. Podemos abri-la ajustando nossas regras de grupo de segurança de entrada para a instância do EC2. Também abrimos a porta 22 para que possamos fazer SSH na instância do EC2. Considere definir o escopo da origem para o endereço IP específico do qual você está acessando a instância do EC2.

Grupos de Segurança

Depois que você estiver conectado à sua instância do EC2, configuramos um ambiente virtual Python e instalamos a API Locust de código aberto por meio da CLI:

virtualenv venv #venv is the virtual environment name, you can change as you desire
source venv/bin/activate #activate virtual environment
pip install locust

Agora estamos prontos para trabalhar com o Locust para testar a carga de nosso endpoint.

teste de gafanhotos

Todos os testes de carga do Locust são conduzidos com base em um arquivo Locust que você fornece. Este arquivo Locust define uma tarefa para o teste de carga; é aqui que definimos o nosso Boto3 chamada de API invoke_endpoint. Veja o seguinte código:

config = Config(
retries = { 'max_attempts': 0, 'mode': 'standard'
}
) self.sagemaker_client = boto3.client('sagemaker-runtime',config=config)
self.endpoint_name = host.split('/')[-1]
self.region = region
self.content_type = content_type
self.payload = payload

No código anterior, ajuste os parâmetros de chamada do endpoint de chamada para atender à chamada de modelo específica. Nós usamos o InvokeEndpoint API usando o seguinte trecho de código no arquivo Locust; este é o nosso ponto de execução do teste de carga. O arquivo Locust que estamos usando é locust_script.py.

def send(self): request_meta = { "request_type": "InvokeEndpoint", "name": "SageMaker", "start_time": time.time(), "response_length": 0, "response": None, "context": {}, "exception": None,
}
start_perf_counter = time.perf_counter() try:
response = self.sagemaker_client.invoke_endpoint(
EndpointName=self.endpoint_name,
Body=self.payload,
ContentType=self.content_type
)
response_body = response["Body"].read()

Agora que temos nosso script Locust pronto, queremos executar testes Locust distribuídos para testar nossa única instância para descobrir quanto tráfego nossa instância pode suportar.

O modo distribuído do Locust é um pouco mais sutil do que um teste Locust de processo único. No modo distribuído, temos um primário e vários trabalhadores. O trabalhador principal instrui os trabalhadores sobre como gerar e controlar os usuários simultâneos que estão enviando uma solicitação. Na nossa distribuído.sh script, vemos por padrão que 240 usuários serão distribuídos entre os 60 trabalhadores. Observe que o --headless O sinalizador na CLI do Locust remove o recurso de interface do usuário do Locust.

#replace with your endpoint name in format https://<<endpoint-name>>
export ENDPOINT_NAME=https://$1 export REGION=us-east-1
export CONTENT_TYPE=application/json
export PAYLOAD='{"inputs": "I am super happy right now."}'
export USERS=240
export WORKERS=60
export RUN_TIME=1m
export LOCUST_UI=false # Use Locust UI .
.
. locust -f $SCRIPT -H $ENDPOINT_NAME --master --expect-workers $WORKERS -u $USERS -t $RUN_TIME --csv results &
.
.
. for (( c=1; c<=$WORKERS; c++ ))
do
locust -f $SCRIPT -H $ENDPOINT_NAME --worker --master-host=localhost &
done

./distributed.sh huggingface-pytorch-inference-2022-10-04-02-46-44-677 #to execute Distributed Locust test

Primeiro, executamos o teste distribuído em uma única instância que suporta o endpoint. A ideia aqui é que queremos maximizar totalmente uma única instância para entender a contagem de instâncias que precisamos para atingir nosso TPS de destino enquanto permanecemos dentro de nossos requisitos de latência. Observe que, se você deseja acessar a interface do usuário, altere o Locust_UI variável de ambiente para True e pegue o IP público de sua instância do EC2 e mapeie a porta 8089 para o URL.

A captura de tela a seguir mostra nossas métricas do CloudWatch.

Métricas do CloudWatch

Por fim, percebemos que, embora tenhamos atingido inicialmente um TPS de 200, começamos a perceber erros 5xx em nossos logs do lado do cliente do EC2, conforme mostrado na captura de tela a seguir.

Também podemos verificar isso observando nossas métricas em nível de instância, especificamente CPUUtilization.

Métricas do CloudWatchAqui notamos CPUUtilization em quase 4,800%. Nossa instância ml.m5.12x.large tem 48 vCPUs (48 * 100 = 4800~). Isso está saturando toda a instância, o que também ajuda a explicar nossos erros 5xx. Vemos também um aumento ModelLatency.

Parece que nossa única instância está sendo derrubada e não tem computação para sustentar uma carga além dos 200 TPS que estamos observando. Nosso TPS alvo é 1000, então vamos tentar aumentar nossa contagem de instâncias para 5. Isso pode ter que ser ainda mais em uma configuração de produção, porque estávamos observando erros em 200 TPS após um certo ponto.

Configurações de endpoint

Vemos na IU do Locust e nos logs do CloudWatch que temos um TPS de quase 1000 com cinco instâncias de suporte ao endpoint.

Gafanhoto

Métricas do CloudWatchSe você começar a ter erros mesmo com esta configuração de hardware, certifique-se de monitorar CPUUtilization para entender a imagem completa por trás de sua hospedagem de endpoint. É crucial entender a utilização de seu hardware para ver se você precisa aumentar ou mesmo reduzir. Às vezes, problemas no nível do contêiner levam a erros 5xx, mas se CPUUtilization é baixo, indica que não é o seu hardware, mas algo no contêiner ou no nível do modelo que pode estar levando a esses problemas (variável de ambiente adequada para o número de trabalhadores não definido, por exemplo). Por outro lado, se você perceber que sua instância está ficando totalmente saturada, é um sinal de que você precisa aumentar a frota de instâncias atual ou experimentar uma instância maior com uma frota menor.

Embora tenhamos aumentado a contagem de instâncias para 5 para lidar com 100 TPS, podemos ver que o ModelLatency métrica ainda é alta. Isso ocorre porque as instâncias estão saturadas. Em geral, sugerimos tentar utilizar os recursos da instância entre 60% e 70%.

limpar

Após o teste de carga, certifique-se de limpar todos os recursos que você não utilizará por meio do console do SageMaker ou do excluir_endpoint Chamada API Boto3. Além disso, certifique-se de interromper sua instância do EC2 ou qualquer configuração de cliente que você tenha para não incorrer em cobranças adicionais.

Resumo

Nesta postagem, descrevemos como você pode carregar o teste de seu endpoint em tempo real do SageMaker. Também discutimos quais métricas você deve avaliar ao carregar o teste de seu endpoint para entender sua divisão de desempenho. Certifique-se de verificar Recomendador de inferência do SageMaker para entender melhor o dimensionamento correto da instância e mais técnicas de otimização de desempenho.


Sobre os autores

Marc Karp é um arquiteto de ML da equipe SageMaker Service. Ele se concentra em ajudar os clientes a projetar, implantar e gerenciar cargas de trabalho de ML em escala. Em seu tempo livre, ele gosta de viajar e explorar novos lugares.

Ram Vegiraju é um arquiteto de ML da equipe SageMaker Service. Ele se concentra em ajudar os clientes a criar e otimizar suas soluções de IA/ML no Amazon SageMaker. Nas horas vagas, adora viajar e escrever.

Carimbo de hora:

Mais de Aprendizado de máquina da AWS