Best practice per il test di carico degli endpoint di inferenza in tempo reale di Amazon SageMaker

Best practice per il test di carico degli endpoint di inferenza in tempo reale di Amazon SageMaker

Nodo di origine: 1889926

Amazon Sage Maker è un servizio di machine learning (ML) completamente gestito. Con SageMaker, data scientist e sviluppatori possono creare e addestrare rapidamente e facilmente modelli ML, quindi distribuirli direttamente in un ambiente ospitato pronto per la produzione. Fornisce un'istanza notebook di authoring Jupyter integrata per un facile accesso alle origini dati per l'esplorazione e l'analisi, in modo da non dover gestire i server. Fornisce anche comune Algoritmi ML che sono ottimizzati per funzionare in modo efficiente con dati estremamente grandi in un ambiente distribuito.

L'inferenza in tempo reale di SageMaker è ideale per i carichi di lavoro che hanno requisiti in tempo reale, interattivi e a bassa latenza. Con l'inferenza in tempo reale di SageMaker, puoi distribuire endpoint REST supportati da un tipo di istanza specifico con una certa quantità di elaborazione e memoria. La distribuzione di un endpoint SageMaker in tempo reale è solo il primo passo nel percorso verso la produzione per molti clienti. Vogliamo essere in grado di massimizzare le prestazioni dell'endpoint per raggiungere un obiettivo di transazioni al secondo (TPS) rispettando i requisiti di latenza. Gran parte dell'ottimizzazione delle prestazioni per l'inferenza consiste nell'assicurarsi di selezionare il tipo di istanza corretto e contare per supportare un endpoint.

Questo post descrive le best practice per testare il carico di un endpoint SageMaker per trovare la configurazione corretta per il numero di istanze e le dimensioni. Questo può aiutarci a comprendere i requisiti minimi dell'istanza con provisioning per soddisfare i nostri requisiti di latenza e TPS. Da lì, ci addentriamo nel modo in cui è possibile monitorare e comprendere le metriche e le prestazioni dell'utilizzo dell'endpoint SageMaker Amazon Cloud Watch metrica.

Per prima cosa confrontiamo le prestazioni del nostro modello su una singola istanza per identificare il TPS che può gestire in base ai nostri requisiti di latenza accettabili. Quindi estrapoliamo i risultati per decidere il numero di istanze di cui abbiamo bisogno per gestire il nostro traffico di produzione. Infine, simuliamo il traffico a livello di produzione e impostiamo test di carico per un endpoint SageMaker in tempo reale per confermare che il nostro endpoint può gestire il carico a livello di produzione. L'intero set di codice per l'esempio è disponibile di seguito Repository GitHub.

Panoramica della soluzione

Per questo post, distribuiamo un pre-addestrato Volto avvolgente modello DistilBERT dal Hub per il viso che abbraccia. Questo modello può eseguire una serie di attività, ma inviamo un payload specifico per l'analisi del sentiment e la classificazione del testo. Con questo payload di esempio, ci sforziamo di raggiungere 1000 TPS.

Distribuisci un endpoint in tempo reale

Questo post presuppone che tu abbia familiarità con la distribuzione di un modello. Fare riferimento a Crea il tuo endpoint e distribuisci il tuo modello per comprendere i meccanismi interni dietro l'hosting di un endpoint. Per ora, possiamo puntare rapidamente a questo modello in Hugging Face Hub e distribuire un endpoint in tempo reale con il seguente frammento di codice:

# 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
)

Proviamo rapidamente il nostro endpoint con il payload di esempio che vogliamo utilizzare per il test di carico:


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

Tieni presente che stiamo supportando l'endpoint utilizzando un singolo Cloud di calcolo elastico di Amazon (Amazon EC2) istanza di tipo ml.m5.12xlarge, che contiene 48 vCPU e 192 GiB di memoria. Il numero di vCPU è una buona indicazione della concorrenza che l'istanza può gestire. In generale, si consiglia di testare diversi tipi di istanza per assicurarsi di disporre di un'istanza con risorse utilizzate correttamente. Per visualizzare un elenco completo delle istanze SageMaker e la relativa potenza di calcolo per l'inferenza in tempo reale, fare riferimento a Prezzi di Amazon SageMaker.

Metriche da monitorare

Prima di poter entrare nel test di carico, è essenziale capire quali metriche monitorare per comprendere la ripartizione delle prestazioni del tuo endpoint SageMaker. CloudWatch è lo strumento di registrazione principale utilizzato da SageMaker per aiutarti a comprendere le diverse metriche che descrivono le prestazioni del tuo endpoint. Puoi utilizzare i log di CloudWatch per eseguire il debug delle chiamate degli endpoint; tutte le istruzioni di registrazione e stampa che hai nel tuo codice di inferenza vengono catturate qui. Per ulteriori informazioni, fare riferimento a Come funziona Amazon CloudWatch.

Esistono due diversi tipi di parametri coperti da CloudWatch per SageMaker: parametri a livello di istanza e di chiamata.

Metriche a livello di istanza

Il primo set di parametri da considerare sono le metriche a livello di istanza: CPUUtilization ed MemoryUtilization (per le istanze basate su GPU, GPUUtilization). Per CPUUtilization, potresti vedere inizialmente percentuali superiori al 100% in CloudWatch. È importante realizzare per CPUUtilization, viene visualizzata la somma di tutti i core della CPU. Ad esempio, se l'istanza dietro l'endpoint contiene 4 vCPU, significa che l'intervallo di utilizzo è fino al 400%. MemoryUtilization, d'altra parte, è compreso tra 0 e 100%.

In particolare, puoi usare CPUUtilization per comprendere meglio se si dispone di una quantità sufficiente o addirittura eccessiva di hardware. Se disponi di un'istanza sottoutilizzata (meno del 30%), potresti potenzialmente ridimensionare il tipo di istanza. Al contrario, se l'utilizzo è intorno all'80-90%, sarebbe vantaggioso scegliere un'istanza con maggiore capacità di calcolo/memoria. Dai nostri test, suggeriamo circa il 60-70% di utilizzo del tuo hardware.

Metriche di chiamata

Come suggerito dal nome, le metriche di chiamata sono il punto in cui possiamo tenere traccia della latenza end-to-end di qualsiasi chiamata al tuo endpoint. È possibile utilizzare le metriche di chiamata per acquisire i conteggi degli errori e il tipo di errori (5xx, 4xx e così via) che l'endpoint potrebbe riscontrare. Ancora più importante, puoi comprendere la suddivisione della latenza delle chiamate dell'endpoint. Molto di questo può essere catturato con ModelLatency ed OverheadLatency metriche, come illustrato nel diagramma seguente.

Latencies

I ModelLatency La metrica acquisisce il tempo impiegato dall'inferenza all'interno del contenitore del modello dietro un endpoint SageMaker. Tieni presente che il contenitore del modello include anche qualsiasi codice o script di inferenza personalizzato passato per l'inferenza. Questa unità viene acquisita in microsecondi come parametro di chiamata e in genere puoi tracciare un percentile su CloudWatch (p99, p90 e così via) per vedere se stai soddisfacendo la latenza target. Tieni presente che diversi fattori possono influire sulla latenza del modello e del contenitore, ad esempio:

  • Script di inferenza personalizzato – Indipendentemente dal fatto che tu abbia implementato il tuo contenitore o utilizzato un contenitore basato su SageMaker con gestori di inferenza personalizzati, è consigliabile profilare lo script per rilevare eventuali operazioni che aggiungono molto tempo alla tua latenza.
  • Protocollo di comunicazione – Prendere in considerazione le connessioni REST e gRPC al server modello all'interno del contenitore modello.
  • Ottimizzazioni del framework del modello – Questo è specifico del framework, ad esempio con TensorFlow, ci sono un certo numero di variabili di ambiente che puoi regolare che sono specifiche di TF Serving. Assicurati di controllare quale contenitore stai utilizzando e se sono presenti ottimizzazioni specifiche del framework che puoi aggiungere all'interno dello script o come variabili di ambiente da inserire nel contenitore.

OverheadLatency viene misurato dal momento in cui SageMaker riceve la richiesta fino a quando non restituisce una risposta al client, meno la latenza del modello. Questa parte è in gran parte al di fuori del tuo controllo e rientra nel tempo impiegato dalle spese generali di SageMaker.

La latenza end-to-end nel suo insieme dipende da una varietà di fattori e non è necessariamente la somma di ModelLatency più OverheadLatency. Ad esempio, se il tuo cliente sta realizzando il file InvokeEndpoint Chiamata API su Internet, dal punto di vista del cliente, la latenza end-to-end sarebbe internet + ModelLatency + OverheadLatency. Pertanto, durante il test del carico dell'endpoint per eseguire un benchmark accurato dell'endpoint stesso, si consiglia di concentrarsi sulle metriche dell'endpoint (ModelLatency, OverheadLatencye InvocationsPerInstance) per eseguire un benchmark accurato dell'endpoint SageMaker. Eventuali problemi relativi alla latenza end-to-end possono quindi essere isolati separatamente.

Alcune domande da considerare per la latenza end-to-end:

  • Dov'è il client che sta invocando il tuo endpoint?
  • Esistono livelli intermedi tra il tuo cliente e il runtime di SageMaker?

Ridimensionamento automatico

In questo post non trattiamo specificamente il ridimensionamento automatico, ma è una considerazione importante per eseguire il provisioning del numero corretto di istanze in base al carico di lavoro. A seconda dei tuoi schemi di traffico, puoi allegare un file criterio di ridimensionamento automatico al tuo endpoint SageMaker. Esistono diverse opzioni di ridimensionamento, ad esempio TargetTrackingScaling, SimpleScalinge StepScaling. Ciò consente al tuo endpoint di ridimensionarsi automaticamente in base al modello di traffico.

Un'opzione comune è il monitoraggio della destinazione, in cui puoi specificare un parametro CloudWatch o un parametro personalizzato che hai definito e ridimensionare in base a quello. Un utilizzo frequente del ridimensionamento automatico sta monitorando il InvocationsPerInstance metrico. Dopo aver identificato un collo di bottiglia in corrispondenza di un determinato TPS, è spesso possibile utilizzarlo come metrica per eseguire la scalabilità orizzontale a un numero maggiore di istanze in modo da poter gestire picchi di traffico. Per un'analisi più approfondita degli endpoint SageMaker con ridimensionamento automatico, fare riferimento a Configurazione degli endpoint di inferenza con scalabilità automatica in Amazon SageMaker.

Test di carico

Sebbene utilizziamo Locust per mostrare come possiamo caricare il test su larga scala, se stai cercando di dimensionare correttamente l'istanza dietro il tuo endpoint, Raccomandatore di inferenza di SageMaker è un'opzione più efficiente. Con gli strumenti di test del carico di terze parti, devi distribuire manualmente gli endpoint su istanze diverse. Con Inference Recommender, puoi semplicemente passare un array dei tipi di istanza su cui vuoi caricare il test e SageMaker si avvierà posti di lavoro per ognuno di questi casi.

Locusta

Per questo esempio, usiamo Locusta, uno strumento di test del carico open source che puoi implementare utilizzando Python. Locust è simile a molti altri strumenti di test di carico open source, ma presenta alcuni vantaggi specifici:

  • Facile da installare – Come dimostriamo in questo post, passeremo un semplice script Python che può essere facilmente sottoposto a refactoring per il tuo endpoint e payload specifici.
  • Distribuito e scalabile – Locust è basato sugli eventi e utilizza gevent sotto il cappuccio. Questo è molto utile per testare carichi di lavoro altamente simultanei e simulare migliaia di utenti simultanei. Puoi ottenere un TPS elevato con un singolo processo che esegue Locust, ma ha anche un generazione di carichi distribuiti funzionalità che consente di ridimensionare a più processi e macchine client, come esploreremo in questo post.
  • Metriche e interfaccia utente delle locuste – Locust acquisisce anche la latenza end-to-end come metrica. Questo può aiutarti a integrare i tuoi parametri CloudWatch per dipingere un quadro completo dei tuoi test. Tutto questo viene catturato nell'interfaccia utente di Locust, dove puoi tenere traccia di utenti simultanei, lavoratori e altro.

Per comprendere ulteriormente Locust, dai un'occhiata al loro documentazione.

Configurazione di Amazon EC2

Puoi configurare Locust in qualsiasi ambiente sia compatibile per te. Per questo post, abbiamo configurato un'istanza EC2 e installato Locust lì per condurre i nostri test. Utilizziamo un'istanza EC5.18 c2xlarge. Anche la potenza di calcolo lato client è qualcosa da considerare. A volte, quando si esaurisce la potenza di calcolo sul lato client, questo spesso non viene acquisito e viene scambiato per un errore dell'endpoint SageMaker. È importante collocare il client in una posizione con potenza di calcolo sufficiente in grado di gestire il carico in cui si esegue il test. Per la nostra istanza EC2, utilizziamo un'AMI Ubuntu Deep Learning, ma puoi utilizzare qualsiasi AMI purché tu possa configurare correttamente Locust sulla macchina. Per capire come avviare e connettersi alla tua istanza EC2, fai riferimento al tutorial Inizia con le istanze Linux di Amazon EC2.

L'interfaccia utente di Locust è accessibile tramite la porta 8089. Possiamo aprirla modificando le nostre regole del gruppo di sicurezza in entrata per l'istanza EC2. Apriamo anche la porta 22 in modo da poter accedere tramite SSH all'istanza EC2. Prendi in considerazione l'ambito dell'origine fino all'indirizzo IP specifico da cui accedi all'istanza EC2.

Gruppi di sicurezza

Dopo esserti connesso alla tua istanza EC2, impostiamo un ambiente virtuale Python e installiamo l'API Locust open source tramite l'interfaccia a riga di comando:

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

Ora siamo pronti a lavorare con Locust per testare il carico del nostro endpoint.

Test delle locuste

Tutti i test di carico delle locuste sono condotti sulla base di a File di locuste che fornisci. Questo file Locust definisce un'attività per il test di carico; è qui che definiamo il nostro Boto3 chiamata API invoke_endpoint. Vedi il seguente codice:

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

Nel codice precedente, regola i parametri di chiamata dell'endpoint di richiamo in modo che si adattino alla chiamata del modello specifico. Noi usiamo il InvokeEndpoint API che utilizza la seguente parte di codice nel file Locust; questo è il nostro punto di esecuzione del test di carico. Il file Locust che stiamo usando è locusta_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()

Ora che abbiamo il nostro script Locust pronto, vogliamo eseguire test Locust distribuiti per sottoporre a stress test la nostra singola istanza per scoprire quanto traffico può gestire la nostra istanza.

La modalità distribuita di Locust è un po' più sfumata rispetto a un test Locust a processo singolo. In modalità distribuita, abbiamo un lavoratore primario e più lavoratori. Il lavoratore principale istruisce i lavoratori su come generare e controllare gli utenti simultanei che inviano una richiesta. Nel nostro distribuito.sh script, vediamo per impostazione predefinita che 240 utenti saranno distribuiti tra i 60 lavoratori. Si noti che il --headless Il flag nella CLI di Locust rimuove la funzionalità dell'interfaccia utente di 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

Per prima cosa eseguiamo il test distribuito su una singola istanza che supporta l'endpoint. L'idea qui è che vogliamo massimizzare completamente una singola istanza per comprendere il numero di istanze di cui abbiamo bisogno per raggiungere il nostro TPS target rimanendo all'interno dei nostri requisiti di latenza. Si noti che se si desidera accedere all'interfaccia utente, modificare il file Locust_UI variabile di ambiente su True e prendi l'IP pubblico della tua istanza EC2 e mappa la porta 8089 all'URL.

Lo screenshot seguente mostra i nostri parametri CloudWatch.

Parametri CloudWatch

Alla fine, notiamo che sebbene inizialmente raggiungiamo un TPS di 200, iniziamo a notare errori 5xx nei nostri registri lato client EC2, come mostrato nello screenshot seguente.

Possiamo anche verificarlo osservando in particolare le nostre metriche a livello di istanza CPUUtilization.

Parametri CloudWatchQui notiamo CPUUtilization a quasi il 4,800%. La nostra istanza ml.m5.12x.large ha 48 vCPU (48 * 100 = 4800~). Questo sta saturando l'intera istanza, il che aiuta anche a spiegare i nostri errori 5xx. Vediamo anche un aumento di ModelLatency.

Sembra che la nostra singola istanza venga rovesciata e non abbia il calcolo per sostenere un carico oltre i 200 TPS che stiamo osservando. Il nostro target TPS è 1000, quindi proviamo ad aumentare il nostro numero di istanze a 5. Questo potrebbe essere ancora di più in un'impostazione di produzione, perché stavamo osservando errori a 200 TPS dopo un certo punto.

Impostazioni dell'endpoint

Vediamo sia nell'interfaccia utente di Locust che nei log di CloudWatch che abbiamo un TPS di quasi 1000 con cinque istanze che supportano l'endpoint.

Locusta

Parametri CloudWatchSe inizi a riscontrare errori anche con questa configurazione hardware, assicurati di monitorare CPUUtilization per comprendere il quadro completo dietro l'hosting dell'endpoint. È fondamentale comprendere l'utilizzo dell'hardware per vedere se è necessario aumentare o diminuire le dimensioni. A volte i problemi a livello di contenitore portano a errori 5xx, ma se CPUUtilization è basso, indica che non è il tuo hardware ma qualcosa a livello di contenitore o modello che potrebbe portare a questi problemi (variabile di ambiente corretta per il numero di lavoratori non impostata, ad esempio). D'altra parte, se noti che la tua istanza si sta saturando completamente, è un segno che devi aumentare il parco istanze corrente o provare un'istanza più grande con un parco istanze più piccolo.

Sebbene abbiamo aumentato il numero di istanze a 5 per gestire 100 TPS, possiamo vedere che il ModelLatency metrica è ancora alta. Ciò è dovuto alla saturazione delle istanze. In generale, suggeriamo di puntare a utilizzare le risorse dell'istanza tra il 60 e il 70%.

ripulire

Dopo il test di carico, assicurati di ripulire tutte le risorse che non utilizzerai tramite la console SageMaker o tramite il elimina_punto finale Chiamata API Boto3. Inoltre, assicurati di arrestare la tua istanza EC2 o qualsiasi configurazione del client di cui hai bisogno per non incorrere in ulteriori addebiti anche lì.

Sommario

In questo post, abbiamo descritto come caricare il test dell'endpoint in tempo reale di SageMaker. Abbiamo anche discusso quali metriche dovresti valutare durante il test di carico del tuo endpoint per comprendere la ripartizione delle prestazioni. Assicurati di controllare Raccomandatore di inferenza di SageMaker per comprendere ulteriormente il corretto dimensionamento delle istanze e ulteriori tecniche di ottimizzazione delle prestazioni.


Informazioni sugli autori

Marc Karp è un ML Architect del team SageMaker Service. Si concentra sull'aiutare i clienti a progettare, distribuire e gestire i carichi di lavoro ML su larga scala. Nel tempo libero ama viaggiare ed esplorare posti nuovi.

Ram Vegiraju è un ML Architect con il team SageMaker Service. Si concentra sull'aiutare i clienti a creare e ottimizzare le loro soluzioni AI/ML su Amazon SageMaker. Nel tempo libero ama viaggiare e scrivere.

Timestamp:

Di più da Apprendimento automatico di AWS