为 Amazon 调整您自己的推理容器 SageMaker - Amazon SageMaker
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

为 Amazon 调整您自己的推理容器 SageMaker

如果您无法将 预先构建的 SageMaker Docker 镜像 Amazon 中列出的任何图像 SageMaker 用于您的用例,则可以构建自己的 Docker 容器,并在其中使用它 SageMaker 进行训练和推理。为了与之兼容 SageMaker,您的容器必须具有以下特性:

  • 您的容器必须在端口上列出 Web 服务器8080

  • 您的容器必须接受对/invocations/ping实时终端节点的POST请求。您发送到这些终端节点的请求必须在 60 秒后返回,并且最大大小为 6 MB。

要了解更多信息以及如何构建自己的 Docker 容器进行训练和推理的示例 SageMaker,请参阅构建自己的算法容器。

以下指南向您展示了如何在 Amazon SageMaker Studio Classic 中使用JupyterLab空间来调整推理容器以适应 SageMaker 托管。该示例使用了 NGINX 网络服务器,Gunicorn 作为 Python Web 服务器网关接口,以及 Flask 作为 Web 应用程序框架。只要容器满足前面列出的要求,您就可以使用不同的应用程序来调整容器。有关使用自己的推理代码的更多信息,请参阅使用托管服务自定义推理代码

调整您的推理容器

使用以下步骤调整您自己的推理容器以与 SageMaker 托管配合使用。以下步骤中显示的示例使用预先训练的命名实体识别 (NER) 模型,该模型使用spaCy自然语言处理 (NLP) 库Python进行以下操作:

  • A Dockerfile 来构建包含的容器 NER 模型。

  • 用于服务的推理脚本 NER 模型。

如果您根据自己的用例调整此示例,则必须使用 Dockerfile 以及部署和提供模型所需的推理脚本。

  1. 使用 Amazon SageMaker Studio Classic(可选)创建 JupyterLab 空间。

    您可以使用任何笔记本来运行脚本,以使您的推理容器适应 SageMaker 托管。此示例向您展示如何使用 JupyterLab Amazon SageMaker Studio Classic 中的空间可以启动 JupyterLab 附带 SageMaker分发映像的应用程序。有关更多信息,请参阅 SageMaker JupyterLab

  2. 上传一个 Docker 文件和推理脚本。

    1. 在您的主目录中创建一个新文件夹。如果你正在使用 JupyterLab,在左上角,选择 “新建文件夹” 图标,然后输入包含您的文件夹的名称 Dockerfile。 在此示例中,该文件夹名为docker_test_folder

    2. 上传一个 Dockerfile 将文本文件放入您的新文件夹。以下是一个示例 Dockerfile 这会创建一个 Docker 包含预训练的命名实体识别 (NER) 模型的容器 spaCy,其中包含运行该示例所需的应用程序和环境变量:

      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/program

      在前面的代码示例中,环境变量PYTHONUNBUFFERED保持 Python 从缓冲标准输出流,这样可以更快地向用户传送日志。环境变量PYTHONDONTWRITEBYTECODE保持 Python 从编写已编译的字节码.pyc文件开始,对于这个用例来说,这些文件是不必要的。调用容器时,环境变量PATH用于标识trainserve程序的位置。

    3. 在新文件夹中创建一个新目录,以包含为模型提供服务的脚本。此示例使用名为的目录NER,其中包含运行此示例所需的以下脚本:

      • predictor.py— A Python 脚本,其中包含用于加载模型并对模型执行推理的逻辑。

      • nginx.conf— 用于配置 Web 服务器的脚本。

      • serve— 启动推理服务器的脚本。

      • wsgi.py— 用于为模型提供服务的辅助脚本。

      重要

      如果您将推理脚本复制到以结尾的笔记本中.ipynb并对其进行重命名,则您的脚本可能包含格式化字符,从而阻止您的端点部署。而是创建一个文本文件并对其进行重命名。

    4. 上传脚本以使您的模型可用于推理。以下是一个名为的脚本示例predictor.py,它使用了 Flask 要提供/ping/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')

      上一个脚本示例中的/ping端点返回的200状态代码为:模型404是否正确加载以及模型加载不正确。/invocations端点处理格式为 JSON,提取输入字段,然后使用 NER 模型用于识别和存储可变实体中的实体。这些区域有:Flask 应用程序返回包含这些实体的响应。有关这些必需的运行状况请求的更多信息,请参阅容器应如何响应运行状况检查 (Ping) 请求

    5. 上传脚本以启动推理服务器。以下脚本示例serve使用调用 Gunicorn 作为应用程序服务器,以及 Nginx 作为 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()

      前面的脚本示例定义了一个信号处理函数sigterm_handler,它会关闭 Nginx 以及 Gunicorn 当它收到SIGTERM信号时进行子处理。一个start_server函数启动信号处理器,启动并监视 Nginx 以及 Gunicorn 子处理,并捕获日志流。

    6. 上传脚本来配置您的 Web 服务器。以下名nginx.conf为的脚本示例配置了 Nginx Web 服务器使用 Gunicorn 作为应用服务器,为您的模型提供推理:

      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 "{}"; } } }

      前面的脚本示例配置 Nginx 要在前台运行,请设置捕获的位置error_log,然后将其定义为 upstream Gunicorn 服务器的套接字袜子。服务器将服务器块配置为监听端口8080,设置客户端请求正文大小和超时值的限制。服务器块将包含/ping/invocations路径的请求转发到 Gunicorn server http://gunicorn,并返回其他路径的404错误。

    7. 上传为模型提供服务所需的任何其他脚本。此示例需要调用以下示例脚本wsgi.py来提供帮助 Gunicorn 查找您的应用程序:

      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

    在该文件夹中docker_test_folder,您的目录结构应包含一个 Dockerfile 还有文件夹 NER。 的 NER 文件夹应包含文件nginx.confpredictor.py、和servewsgi.py如下所示:

    The Dockerfile structure has inference scripts under the NER directory next to the Dockerfile.

  3. 建造自己的容器。

    从该文件夹docker_test_folder中构建你的 Docker 容器。以下示例命令将构建 Docker 在您的中配置的容器 Dockerfile:

    ! docker build -t byo-container-test .

    前面的命令将在当前工作目录byo-container-test中构建一个名为的容器。有关该的更多信息 Docker 生成参数,请参阅生成参数

    注意

    如果你收到以下错误消息 Docker 找不到 Dockerfile,请确保 Dockerfile 名字正确,已保存到目录中。

    unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/ec2-user/SageMaker/docker_test_folder/Dockerfile: no such file or directory

    Docker 查找一个专门名为的文件 Dockerfile 在当前目录中没有任何扩展名。如果您将其命名为其他名称,则可以使用-f 标志手动传入文件名。例如,如果你把你的名字命名为 Dockerfile 如同 Dockerfile-text.txt,建造你的 Docker 容器使用标-f志,然后是你的文件,如下所示:

    ! docker build -t byo-container-test -f Dockerfile-text.txt .
  4. 推你的 Docker 图片到亚马逊弹性容器注册表 (AmazonECR)

    在笔记本手机中,按下你的 Docker 图像到ECR. 以下代码示例向您展示了如何在本地构建容器、登录并将其推送到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}

    在前面的示例中,展示了如何执行以下必要步骤将示例 Docker 容器推送到:ECR

    1. 将算法名称定义为sm-pretrained-spacy

    2. serve文件放进去 NER 可执行文件夹。

    3. 设置 Amazon Web Services 区域.

    4. ECR如果尚不存在,则创建一个。

    5. 登录到ECR.

    6. 构建 Docker 本地容器。

    7. 推动 Docker 图片到ECR.

  5. 设置 SageMaker 客户端

    如果要使用 SageMaker 托管服务进行推理,则必须创建模型、创建端点配置并创建终端点。为了从您的终端节点获取推论,您可以使用 SageMaker boto3 用于调用您的端点的运行时客户端。以下代码向您展示了如何使用 SageMaker boto3 SageMaker 客户端设置客户端和 SageMaker 运行时客户端:

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

    在前面的代码示例中,未使用 Amazon S3 存储桶,而是作为注释插入,以展示如何存储模型构件。

    如果您在运行前面的代码示例后收到权限错误,则可能需要为IAM角色添加权限。有关IAM角色的更多信息,请参阅Amazon SageMaker 角色管理器。有关为当前角色添加权限的更多信息,请参阅Amazon Amazon 托管政策 SageMaker

  6. 创建您的模型。

    如果要使用 SageMaker 托管服务进行推理,则必须在中创建模型。 SageMaker以下代码示例向您展示了如何创建 spaCy NER 里面的模型 SageMaker:

    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'])

    前面的代码示例演示了s3_bucket如果您要model_url使用步骤 5 中注释中的 Amazon S3 存储桶,则如何使用来定义,ECRURI并为容器映像定义。前面的代码示例定义ml.c5d.18xlarge为实例类型。您也可以选择不同的实例类型。有关可用实例类型的更多信息,请参阅 Amazon EC2 实例类型

    在前面的代码示例中,Image关键指向容器镜像URI。该create_model_response定义使用创建模型,并返回模型名称、角色和包含容器信息的列表。create_model method

    上一个脚本的输出示例如下:

    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
    1. 配置和创建端点

      要使用 SageMaker 托管进行推理,还必须配置和创建终端节点。 SageMaker 将使用此端点进行推理。以下配置示例显示如何使用您之前定义的实例类型和型号名称生成和配置终端节点:

      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'])

      在前面的配置示例中,将model_name与使用时间戳创建的唯一端点配置名称create_endpoint_config_responseendpoint_config_name相关联。

      上一个脚本的输出示例如下:

      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-SS

      有关终端节点错误的更多信息,请参阅创建或更新 SageMaker 终端节点时,为什么我的 Amazon 终端节点会进入故障状态?

    2. 创建终端节点并等待终端节点投入使用。

      以下代码示例使用上一个配置示例中的配置创建端点并部署模型:

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

      在前面的代码示例中,该create_endpoint方法使用在上一个代码示例中创建的生成的终端节点名称创建终端节点,并打印该终端节点的 Amazon 资源名称。该describe_endpoint方法返回有关端点及其状态的信息。 SageMaker 服务员等待端点投入使用。

  7. 测试您的终端节点。

    终端节点投入使用后,向您的终端节点发送调用请求。以下代码示例显示了如何向您的终端节点发送测试请求:

    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'] result

    在前面的代码示例中,该方法将json.dumps序列化为格式request_body为的字符串,JSON并将其保存在变量 payload 中。然后, SageMaker 运行时客户端使用调用端点方法向您的终端节点发送有效负载。结果包含提取输出字段后来自终端节点的响应。

    前面的代码示例应返回以下输出:

    [['NER', 'ORG'], ['America', 'GPE'], ['Amazon', 'ORG'], ['Microsoft', 'ORG'], ['Seattle', 'GPE']]
  8. 删除您的终端节点

    完成调用后,请删除您的终端节点以节省资源。以下代码示例向您展示了如何删除终端节点:

    sm_client.delete_endpoint(EndpointName=endpoint_name) sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name) sm_client.delete_model(ModelName=model_name)

    有关包含此示例中代码的完整笔记本,请参阅 BYOC-Single-Model。