Adattare un container di inferenza personalizzato per Amazon SageMaker AI
Se non è possibile utilizzare alcuna immagine elencata in Immagini Docker di SageMaker AI predefinite di Amazon SageMaker AI per un caso d’uso specifico, è possibile creare un container Docker e utilizzarlo all’interno di SageMaker AI per l’addestramento e l’inferenza. Per essere compatibile con SageMaker AI, il container deve avere le seguenti caratteristiche:
-
Il container deve avere un elenco di server web sulla porta
8080. -
Il container deve accettare richieste
POSTverso gli endpoint/invocationse/pingin tempo reale. Le richieste inviate a questi endpoint devono essere restituite entro 60 secondi per le risposte normali e 8 minuti per le risposte in streaming, nonché avere una dimensione massima di 25 MB.
Per ulteriori informazioni e un esempio di come creare un container Docker per l’addestramento e l’inferenza con SageMaker AI, consulta Building your own algorithm container
La seguente guida mostra come utilizzare uno spazio JupyterLab con Amazon SageMaker Studio Classic per adattare un container di inferenza in modo che sia compatibile con l’hosting di SageMaker AI. L’esempio utilizza un server web NGINX, Gunicorn come interfaccia gateway per server Web Python e Flask come framework di applicazioni Web. È possibile utilizzare diverse applicazioni per adattare il container, a condizione che vengano soddisfatti i requisiti elencati in precedenza. Per ulteriori informazioni sull’uso del tuo codice di inferenza, consulta Codice di inferenza personalizzato con servizi di hosting.
Adattare un container di inferenza
Attieniti alla seguente procedura per adattare il tuo container di inferenza in modo che sia compatibile con l’hosting di SageMaker AI. L’esempio mostrato nei passaggi seguenti utilizza un modello NER (Named Entity Recognition)Python e:
-
un Dockerfile per creare il container che contiene il modello NER;
-
script di inferenza per servire il modello NER.
Adattando questo esempio al tuo caso d’uso, è necessario utilizzare un Dockerfile e gli script di inferenza necessari per implementare e gestire il modello.
-
Crea uno spazio JupyterLab con Amazon SageMaker Studio Classic (opzionale).
È possibile utilizzare qualsiasi notebook per eseguire script al fine di adattare il container di inferenza all’hosting di SageMaker AI. Questo esempio mostra come utilizzare uno spazio JupyterLab all’interno di Amazon SageMaker Studio Classic per avviare un’applicazione JupyterLab che include un’immagine di distribuzione SageMaker AI. Per ulteriori informazioni, consulta SageMaker JupyterLab.
-
Carica un Dockerfile e script di inferenza.
-
Crea una nuova cartella nella directory home. Se utilizzi JupyterLab, nell’angolo in alto a sinistra, scegli l’icona New Folder e inserisci un nome di cartella per il tuo Dockerfile. Per questo esempio, la cartella è denominata
docker_test_folder. -
Carica un file di testo Dockerfile nella nuova cartella. Di seguito è riportato un Dockerfile di esempio che crea un container Docker con un modello NER
preaddestrato di spaCy , le applicazioni e le variabili di ambiente necessarie per eseguire l’esempio: FROM python:3.8 RUN apt-get -y update && apt-get install -y --no-install-recommends \ wget \ python3 \ nginx \ ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && \ pip install flask gevent gunicorn && \ rm -rf /root/.cache #pre-trained model package installation RUN pip install spacy RUN python -m spacy download en # Set environment variables ENV PYTHONUNBUFFERED=TRUE ENV PYTHONDONTWRITEBYTECODE=TRUE ENV PATH="/opt/program:${PATH}" COPY NER /opt/program WORKDIR /opt/programNell’esempio di codice precedente, la variabile di ambiente
PYTHONUNBUFFEREDimpedisce il buffering del flusso di output standard da parte di Python, permettendo una consegna più rapida dei log all’utente. La variabile di ambientePYTHONDONTWRITEBYTECODEimpedisce la scrittura di file.pycbytecode compilati da parte di Python, non necessari in questo caso d’uso. La variabile di ambientePATHconsente di identificare la posizione dei programmitraineservequando viene invocato il container. -
Crea una nuova directory all’interno della nuova cartella per contenere gli script di gestione del modello. Questo esempio utilizza una directory denominata
NER, che contiene i seguenti script necessari per eseguire questo esempio:-
predictor.py- Uno script Python contenente la logica per caricare ed eseguire l’inferenza con il modello. -
nginx.conf- Uno script per configurare un server web. -
serve- Uno script che avvia un server di inferenza. -
wsgi.py- Uno script di assistenza per gestire un modello.
Importante
Copiando gli script di inferenza in un notebook che termina con
.ipynbe rinominandoli, gli script potrebbero contenere caratteri di formattazione che impediscono l’implementazione dell’endpoint. Crea invece un file di testo e rinominalo. -
-
Carica uno script per rendere il modello disponibile per l’inferenza. Di seguito è riportato uno script di esempio denominato
predictor.pyche utilizza Flask per fornire gli endpoint/pinge/invocations:from flask import Flask import flask import spacy import os import json import logging #Load in model nlp = spacy.load('en_core_web_sm') #If you plan to use a your own model artifacts, #your model artifacts should be stored in /opt/ml/model/ # The flask app for serving predictions app = Flask(__name__) @app.route('/ping', methods=['GET']) def ping(): # Check if the classifier was loaded correctly health = nlp is not None status = 200 if health else 404 return flask.Response(response= '\n', status=status, mimetype='application/json') @app.route('/invocations', methods=['POST']) def transformation(): #Process input input_json = flask.request.get_json() resp = input_json['input'] #NER doc = nlp(resp) entities = [(X.text, X.label_) for X in doc.ents] # Transform predictions to JSON result = { 'output': entities } resultjson = json.dumps(result) return flask.Response(response=resultjson, status=200, mimetype='application/json')L’endpoint
/pingdell’esempio di script precedente restituisce un codice di stato200se il modello è stato caricato correttamente o404se il modello è stato caricato in modo errato. L’endpoint/invocationselabora una richiesta formattata in JSON, estrae il campo di input e utilizza il modello NER per identificare e archiviare entità nelle entità variabili. L’applicazione Flask restituisce la risposta che contiene queste entità. Per ulteriori informazioni su tali richieste di integrità necessarie, consulta Come il tuo container deve rispondere alle richieste di controllo dello stato (Ping). -
Carica uno script per avviare un server di inferenza. Il seguente esempio di script chiama
serveutilizzando Gunicorn come server delle applicazioni e Nginx come server web:#!/usr/bin/env python # This file implements the scoring service shell. You don't necessarily need to modify it for various # algorithms. It starts nginx and gunicorn with the correct configurations and then simply waits until # gunicorn exits. # # The flask server is specified to be the app object in wsgi.py # # We set the following parameters: # # Parameter Environment Variable Default Value # --------- -------------------- ------------- # number of workers MODEL_SERVER_WORKERS the number of CPU cores # timeout MODEL_SERVER_TIMEOUT 60 seconds import multiprocessing import os import signal import subprocess import sys cpu_count = multiprocessing.cpu_count() model_server_timeout = os.environ.get('MODEL_SERVER_TIMEOUT', 60) model_server_workers = int(os.environ.get('MODEL_SERVER_WORKERS', cpu_count)) def sigterm_handler(nginx_pid, gunicorn_pid): try: os.kill(nginx_pid, signal.SIGQUIT) except OSError: pass try: os.kill(gunicorn_pid, signal.SIGTERM) except OSError: pass sys.exit(0) def start_server(): print('Starting the inference server with {} workers.'.format(model_server_workers)) # link the log streams to stdout/err so they will be logged to the container logs subprocess.check_call(['ln', '-sf', '/dev/stdout', '/var/log/nginx/access.log']) subprocess.check_call(['ln', '-sf', '/dev/stderr', '/var/log/nginx/error.log']) nginx = subprocess.Popen(['nginx', '-c', '/opt/program/nginx.conf']) gunicorn = subprocess.Popen(['gunicorn', '--timeout', str(model_server_timeout), '-k', 'sync', '-b', 'unix:/tmp/gunicorn.sock', '-w', str(model_server_workers), 'wsgi:app']) signal.signal(signal.SIGTERM, lambda a, b: sigterm_handler(nginx.pid, gunicorn.pid)) # Exit the inference server upon exit of either subprocess pids = set([nginx.pid, gunicorn.pid]) while True: pid, _ = os.wait() if pid in pids: break sigterm_handler(nginx.pid, gunicorn.pid) print('Inference server exiting') # The main routine to invoke the start function. if __name__ == '__main__': start_server()L’esempio di script precedente definisce una funzione di gestore di segnale denominata
sigterm_handler, che arresta i sottoprocessi Nginx e Gunicorn quando riceve un segnaleSIGTERM. Una funzionestart_serveravvia il gestore di segnale, monitora i sottoprocessi Nginx e Gunicorn e acquisisce flussi di log. -
Carica uno script per configurare il server web. Il seguente esempio di script, denominato
nginx.conf, configura un server web Nginx utilizzando Gunicorn come server di applicazioni per gestire il modello per l’inferenza:worker_processes 1; daemon off; # Prevent forking pid /tmp/nginx.pid; error_log /var/log/nginx/error.log; events { # defaults } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log combined; upstream gunicorn { server unix:/tmp/gunicorn.sock; } server { listen 8080 deferred; client_max_body_size 5m; keepalive_timeout 5; proxy_read_timeout 1200s; location ~ ^/(ping|invocations) { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://gunicorn; } location / { return 404 "{}"; } } }L’esempio di script precedente configura Nginx per l’esecuzione in primo piano, imposta la posizione in cui acquisire il file
error_loge definisceupstreamcome socket sock del server Gunicorn. Il server configura il blocco server per l’ascolto sulla porta8080e imposta limiti alla dimensione del corpo della richiesta del client e ai valori di timeout. Il blocco server inoltra le richieste contenenti uno dei due percorsi/pingo/invocationsa Gunicorn, all’indirizzo delserver http://gunicorn, e restituisce un errore404per gli altri percorsi. -
Carica tutti gli altri script necessari per gestire il tuo modello. Questo esempio richiede il seguente script di esempio denominato
wsgi.pyper consentire a Gunicorn di individuare la tua applicazione:import predictor as myapp # This is just a simple wrapper for gunicorn to find your app. # If you want to change the algorithm file, simply change "predictor" above to the # new file. app = myapp.app
Dalla cartella
docker_test_folder, la struttura di directory deve contenere un Dockerfile e la cartella NER. La cartella NER deve contenere i filenginx.conf,predictor.py,serveewsgi.py, come illustrato di seguito:
-
-
Crea il tuo container.
Dalla cartella
docker_test_folder, crea il tuo container Docker. Il seguente comando di esempio crea il container Docker configurato nel tuo Dockerfile:! docker build -t byo-container-test .Il comando precedente crea un container denominato
byo-container-testnella directory di lavoro corrente. Per ulteriori informazioni sui parametri di generazione di Docker, consulta Build variables. Nota
Se visualizzi il seguente messaggio di errore che indica che Docker non riesce a trovare il Dockerfile, assicurati che il Dockerfile abbia il nome corretto e che sia stato salvato nella directory.
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/ec2-user/SageMaker/docker_test_folder/Dockerfile: no such file or directoryDocker cerca un file chiamato specificamente Dockerfile senza alcuna estensione all’interno della directory corrente. Se è stato rinominato, è possibile passare il nome del file manualmente con il flag -f. Ad esempio, se hai denominato il Dockerfile Dockerfile-text.txt, crea il container Docker utilizzando il flag
-fseguito dal file, come illustrato di seguito:! docker build -t byo-container-test -f Dockerfile-text.txt . -
Invia l’immagine Docker a un registro Amazon Elastic Container Registry (Amazon ECR).
Nella cella di un notebook, invia l’immagine Docker a un registro ECR. Il seguente esempio di codice mostra come creare il container localmente, eseguire l’accesso e inviarlo a un registro ECR:
%%sh # Name of algo -> ECR algorithm_name=sm-pretrained-spacy #make serve executable chmod +x NER/serve account=$(aws sts get-caller-identity --query Account --output text) # Region, defaults to us-west-2 region=$(aws configure get region) region=${region:-us-east-1} fullname="${account}.dkr.ecr.${region}.amazonaws.com/${algorithm_name}:latest" # If the repository doesn't exist in ECR, create it. aws ecr describe-repositories --repository-names "${algorithm_name}" > /dev/null 2>&1 if [ $? -ne 0 ] then aws ecr create-repository --repository-name "${algorithm_name}" > /dev/nullfi # Get the login command from ECR and execute it directly aws ecr get-login-password --region ${region}|docker login --username AWS --password-stdin ${fullname} # Build the docker image locally with the image name and then push it to ECR # with the full name. docker build -t ${algorithm_name} . docker tag ${algorithm_name} ${fullname} docker push ${fullname}Nell’esempio precedente viene illustrato come eseguire i seguenti passaggi necessari per inviare il container Docker di esempio a un registro ECR:
-
Definire il nome dell’algoritmo come
sm-pretrained-spacy. -
Rendere eseguibile il file
serveall’interno della cartella NER. -
Impostare la Regione AWS.
-
Creare un registro ECR, se non esiste già.
-
Accedere al registro ECR.
-
Creare il container Docker in locale.
-
Inviare l’immagine Docker al registro ECR.
-
-
Configura il client SageMaker AI.
Per utilizzare i servizi di hosting di SageMaker AI per l’inferenza, è necessario creare un modello
, creare una configurazione dell’endpoint e creare un endpoint . Per ottenere inferenze dall’endpoint, è possibile utilizzare il client SageMaker AI Runtime di boto3 per invocare l’endpoint. Il codice seguente mostra come configurare sia il client SageMaker AI che il client SageMaker Runtime utilizzando il client SageMaker AI di boto3 : import boto3 from sagemaker import get_execution_role sm_client = boto3.client(service_name='sagemaker') runtime_sm_client = boto3.client(service_name='sagemaker-runtime') account_id = boto3.client('sts').get_caller_identity()['Account'] region = boto3.Session().region_name #used to store model artifacts which SageMaker AI will extract to /opt/ml/model in the container, #in this example case we will not be making use of S3 to store the model artifacts #s3_bucket = '<S3Bucket>' role = get_execution_role()Nell’esempio di codice precedente, il bucket Amazon S3 non viene utilizzato, ma inserito come commento per mostrare come archiviare gli artefatti del modello.
Se ricevi un errore di autorizzazione dopo aver eseguito l’esempio di codice precedente, potresti dover aggiungere autorizzazioni al tuo ruolo IAM. Per ulteriori informazioni sui ruoli IAM, consultare Gestione dei ruoli di Amazon SageMaker. Per ulteriori informazioni sull’aggiunta di autorizzazioni al ruolo corrente, consulta Policy gestite da AWS per Amazon SageMaker AI.
-
Crea il tuo modello.
Per utilizzare i servizi di hosting di SageMaker AI per l’inferenza, è necessario creare un modello in SageMaker AI. Il seguente esempio di codice mostra come creare il modello NER di spaCy all’interno di SageMaker AI:
from time import gmtime, strftime model_name = 'spacy-nermodel-' + strftime("%Y-%m-%d-%H-%M-%S", gmtime()) # MODEL S3 URL containing model atrifacts as either model.tar.gz or extracted artifacts. # Here we are not #model_url = 's3://{}/spacy/'.format(s3_bucket) container = '{}.dkr.ecr.{}.amazonaws.com/sm-pretrained-spacy:latest'.format(account_id, region) instance_type = 'ml.c5d.18xlarge' print('Model name: ' + model_name) #print('Model data Url: ' + model_url) print('Container image: ' + container) container = { 'Image': container } create_model_response = sm_client.create_model( ModelName = model_name, ExecutionRoleArn = role, Containers = [container]) print("Model Arn: " + create_model_response['ModelArn'])L’esempio di codice precedente mostra come definire una variabile
model_urlmediantes3_bucket, in caso di utilizzo del bucket Amazon S3 come indicato nei commenti alla fase 5, e definisce l’URI ECR per l’immagine del container. Gli esempi di codice precedenti definisconoml.c5d.18xlargecome tipo di istanza. È anche possibile scegliere un tipo di istanza diverso. Per ulteriori informazioni sui tipi di istanza disponibili, consulta Tipi di istanze Amazon EC2. Nell’esempio di codice precedente, la chiave
Imagepunta all’URI dell’immagine del container. La definizionecreate_model_responseutilizza ilcreate_model methodper creare un modello e restituire il nome del modello, il ruolo e un elenco contenente le informazioni sul container.Segue un esempio di output dello script precedente:
Model name: spacy-nermodel-YYYY-MM-DD-HH-MM-SS Model data Url: s3://spacy-sagemaker-us-east-1-bucket/spacy/ Container image: 123456789012.dkr.ecr.us-east-2.amazonaws.com/sm-pretrained-spacy:latest Model Arn: arn:aws:sagemaker:us-east-2:123456789012:model/spacy-nermodel-YYYY-MM-DD-HH-MM-SS -
-
Configura e crea un endpoint
Per utilizzare l’hosting di SageMaker AI per l’inferenza, è inoltre necessario configurare e creare un endpoint. SageMaker AI utilizza questo endpoint per l’inferenza. Il seguente esempio di configurazione mostra come generare e configurare un endpoint con il tipo di istanza e il nome di modello definiti in precedenza:
endpoint_config_name = 'spacy-ner-config' + strftime("%Y-%m-%d-%H-%M-%S", gmtime()) print('Endpoint config name: ' + endpoint_config_name) create_endpoint_config_response = sm_client.create_endpoint_config( EndpointConfigName = endpoint_config_name, ProductionVariants=[{ 'InstanceType': instance_type, 'InitialInstanceCount': 1, 'InitialVariantWeight': 1, 'ModelName': model_name, 'VariantName': 'AllTraffic'}]) print("Endpoint config Arn: " + create_endpoint_config_response['EndpointConfigArn'])Nell’esempio di configurazione precedente,
create_endpoint_config_responseassociamodel_namea un nome di configurazione dell’endpoint univoco,endpoint_config_name, creato con un timestamp.Segue un esempio di output dello script precedente:
Endpoint config name: spacy-ner-configYYYY-MM-DD-HH-MM-SS Endpoint config Arn: arn:aws:sagemaker:us-east-2:123456789012:endpoint-config/spacy-ner-config-MM-DD-HH-MM-SSPer ulteriori informazioni sugli errori degli endpoint, consulta Perché il mio endpoint Amazon SageMaker AI entra in stato di errore quando creo o aggiorno un endpoint?
-
Crea un endpoint e attendi che sia attivo.
Il seguente esempio di codice crea l’endpoint utilizzando la configurazione dell’esempio di configurazione precedente e implementa il modello:
%%time import time endpoint_name = 'spacy-ner-endpoint' + strftime("%Y-%m-%d-%H-%M-%S", gmtime()) print('Endpoint name: ' + endpoint_name) create_endpoint_response = sm_client.create_endpoint( EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name) print('Endpoint Arn: ' + create_endpoint_response['EndpointArn']) resp = sm_client.describe_endpoint(EndpointName=endpoint_name) status = resp['EndpointStatus'] print("Endpoint Status: " + status) print('Waiting for {} endpoint to be in service...'.format(endpoint_name)) waiter = sm_client.get_waiter('endpoint_in_service') waiter.wait(EndpointName=endpoint_name)Nell’esempio di codice precedente, il metodo
create_endpointcrea l’endpoint con il nome dell’endpoint generato creato nell’esempio di codice precedente e visualizza il nome della risorsa Amazon (ARN) dell’endpoint. Il metododescribe_endpointrestituisce informazioni sull’endpoint e sul relativo stato. Un waiter SageMaker AI attende che l’endpoint sia attivo.
-
-
Testa l’endpoint.
Una volta che l’endpoint è attivo, invia una richiesta di invocazione
all’endpoint. L’esempio di codice seguente mostra come inviare una richiesta di test all’endpoint: import json content_type = "application/json" request_body = {"input": "This is a test with NER in America with \ Amazon and Microsoft in Seattle, writing random stuff."} #Serialize data for endpoint #data = json.loads(json.dumps(request_body)) payload = json.dumps(request_body) #Endpoint invocation response = runtime_sm_client.invoke_endpoint( EndpointName=endpoint_name, ContentType=content_type, Body=payload) #Parse results result = json.loads(response['Body'].read().decode())['output'] resultNell’esempio di codice precedente, il metodo
json.dumpsserializzarequest_bodyin una stringa formattata in JSON e la salva nella variabile payload. Quindi il client SageMaker AI Runtime utilizza il metodo invoke_endpointper inviare il payload all’endpoint. Il risultato contiene la risposta dell’endpoint dopo l’estrazione del campo di output. L’esempio di codice precedente deve restituire l’output seguente:
[['NER', 'ORG'], ['America', 'GPE'], ['Amazon', 'ORG'], ['Microsoft', 'ORG'], ['Seattle', 'GPE']] -
Elimina l’endpoint.
Dopo aver completato le invocazioni, elimina l’endpoint per risparmiare risorse. L’esempio di codice seguente mostra come eliminare l’endpoint:
sm_client.delete_endpoint(EndpointName=endpoint_name) sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name) sm_client.delete_model(ModelName=model_name)Per un notebook completo contenente il codice in questo esempio, consulta BYOC-Single-Model
.