{ "cells": [ { "cell_type": "markdown", "id": "e2527f22", "metadata": {}, "source": [ "# Deploy SageMaker Serverless Endpoint\n", "\n", "## Sentiment Binary Classification (fine-tuning with KoELECTRA-Small-v3 model and Naver Sentiment Movie Corpus dataset)\n", "\n", "- KoELECTRA: https://github.com/monologg/KoELECTRA\n", "- Naver Sentiment Movie Corpus Dataset: https://github.com/e9t/nsmc\n", "\n", "---\n", "\n", "## Overview\n", "\n", "Amazon SageMaker Serverless Inference는 re:Invent 2021에 런칭된 신규 추론 옵션으로 호스팅 인프라 관리에 대한 부담 없이 머신 러닝을 모델을 쉽게 배포하고 확장할 수 있도록 제작된 신규 추론 옵션입니다. SageMaker Serverless Inference는 컴퓨팅 리소스를 자동으로 시작하고 트래픽에 따라 자동으로 스케일 인/아웃을 수행하므로 인스턴스 유형을 선택하거나 스케일링 정책을 관리할 필요가 없습니다. 따라서, 트래픽 급증 사이에 유휴 기간이 있고 콜드 스타트를 허용할 수 있는 워크로드에 이상적입니다.\n", "\n", "## Difference from Lambda Serverless Inference\n", "\n", "\n", "### Lambda Serverless Inference\n", "\n", "- Lambda 컨테이너용 도커 이미지 빌드/디버그 후 Amazon ECR(Amazon Elastic Container Registry)에 푸시\n", "- Option 1: Lambda 함수를 생성하여 직접 모델 배포 수행\n", "- Option 2: SageMaker API로 SageMaker에서 모델 배포 수행 (`LambdaModel` 및 `LambdaPredictor` 리소스를 순차적으로 생성) 단, Option 2를 사용하는 경우 적절한 권한을 직접 설정해 줘야 합니다.\n", " - SageMaker과 연결된 role 대해 ECR 억세스를 허용하는 policy 생성 및 연결\n", " - SageMaker 노트북에서 lambda를 실행할 수 있는 role 생성\n", " - Lambda 함수가 ECR private 리포지토리에 연결하는 억세스를 허용하는 policy 생성 및 연결 \n", "\n", "\n", "### SageMaker Serverless Inference\n", "\n", "기존 Endpoint 배포 코드에서 Endpoint 설정만 변경해 주시면 되며, 별도의 도커 이미지 빌드가 필요 없기에 쉽고 빠르게 서버리스 추론을 수행할 수 있습니다.\n", "\n", "**주의**\n", "- 현재 서울 리전을 지원하지 않기 때문에 아래 리전 중 하나를 선택해서 수행하셔야 합니다.\n", " - 현재 지원하는 리전: US East (Northern Virginia), US East (Ohio), US West (Oregon), EU (Ireland), Asia Pacific (Tokyo) and Asia Pacific (Sydney)\n", "- boto3, botocore, sagemaker, awscli는 2021년 12월 버전 이후를 사용하셔야 합니다." ] }, { "cell_type": "markdown", "id": "16b6b42a", "metadata": {}, "source": [ "
\n", "\n", "## 1. Upload Model Artifacts\n", "---\n", "\n", "모델을 아카이빙하여 S3로 업로드합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "7ff9b59c", "metadata": {}, "outputs": [], "source": [ "!pip install -qU sagemaker botocore boto3 awscli\n", "!pip install --ignore-installed PyYAML\n", "!pip install transformers==4.12.5" ] }, { "cell_type": "code", "execution_count": null, "id": "00e80119", "metadata": {}, "outputs": [], "source": [ "import torch\n", "import torchvision\n", "import torchvision.models as models\n", "import sagemaker\n", "from sagemaker import get_execution_role\n", "from sagemaker.utils import name_from_base\n", "from sagemaker.pytorch import PyTorchModel\n", "import boto3\n", "import datetime\n", "import time\n", "from time import strftime,gmtime\n", "import json\n", "import os\n", "import io\n", "import torchvision.transforms as transforms\n", "from src.utils import print_outputs, upload_model_artifact_to_s3, NLPPredictor \n", "\n", "role = get_execution_role()\n", "boto_session = boto3.session.Session()\n", "sm_session = sagemaker.session.Session()\n", "sm_client = boto_session.client(\"sagemaker\")\n", "sm_runtime = boto_session.client(\"sagemaker-runtime\")\n", "region = boto_session.region_name\n", "bucket = sm_session.default_bucket()\n", "prefix = 'serverless-inference-kornlp-nsmc'\n", "\n", "print(f'region = {region}')\n", "print(f'role = {role}')\n", "print(f'bucket = {bucket}')\n", "print(f'prefix = {prefix}')" ] }, { "cell_type": "code", "execution_count": null, "id": "1b5ae146", "metadata": {}, "outputs": [], "source": [ "model_variant = 'modelA'\n", "nlp_task = 'nsmc'\n", "model_path = f'model-{nlp_task}'\n", "model_s3_uri = upload_model_artifact_to_s3(model_variant, model_path, bucket, prefix)" ] }, { "cell_type": "markdown", "id": "64c8e40e", "metadata": {}, "source": [ "
\n", "\n", "## 2. Create SageMaker Serverless Endpoint\n", "---\n", "\n", "SageMaker Serverless Endpoint는 기존 SageMaker 리얼타임 엔드포인트 배포와 99% 유사합니다. 1%의 차이가 무엇일까요? Endpoint 구성 설정 시, ServerlessConfig에서 메모리 크기(`MemorySizeInMB`), 최대 동시 접속(`MaxConcurrency`)에 대한 파라메터만 추가하시면 됩니다.\n", "\n", "```python\n", "sm_client.create_endpoint_config(\n", " ...\n", " \"ServerlessConfig\": {\n", " \"MemorySizeInMB\": 2048,\n", " \"MaxConcurrency\": 20\n", " }\n", ")\n", "```\n", "\n", "자세한 내용은 아래 링크를 참조해 주세요.\n", "- Amazon SageMaker Developer Guide - Serverless Inference: https://docs.aws.amazon.com/sagemaker/latest/dg/serverless-endpoints.html" ] }, { "cell_type": "markdown", "id": "f427e61c", "metadata": {}, "source": [ "### Create Inference containter definition for Model" ] }, { "cell_type": "code", "execution_count": null, "id": "f1f8c748", "metadata": {}, "outputs": [], "source": [ "from sagemaker.image_uris import retrieve\n", "\n", "deploy_instance_type = 'ml.m5.xlarge'\n", "pt_ecr_image_uri = retrieve(\n", " framework='pytorch',\n", " region=region,\n", " version='1.7.1',\n", " py_version='py3',\n", " instance_type = deploy_instance_type,\n", " accelerator_type=None,\n", " image_scope='inference'\n", ")" ] }, { "cell_type": "markdown", "id": "fe77df3e", "metadata": {}, "source": [ "### Create a SageMaker Model\n", "\n", "`create_model` API를 호출하여 위 코드 셀에서 생성한 컨테이너의 정의를 포함하는 모델을 생성합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "4f505b02", "metadata": {}, "outputs": [], "source": [ "model_name = f\"KorNLPServerless-{nlp_task}-{strftime('%Y-%m-%d-%H-%M-%S', gmtime())}\"\n", "\n", "create_model_response = sm_client.create_model(\n", " ModelName=model_name,\n", " Containers=[\n", " {\n", " \"Image\": pt_ecr_image_uri,\n", " \"Mode\": \"SingleModel\",\n", " \"ModelDataUrl\": model_s3_uri,\n", " \"Environment\": {\n", " \"SAGEMAKER_CONTAINER_LOG_LEVEL\": \"20\",\n", " \"SAGEMAKER_PROGRAM\": \"inference_nsmc.py\",\n", " \"SAGEMAKER_SUBMIT_DIRECTORY\": model_s3_uri,\n", " }, \n", " } \n", " \n", " ],\n", " ExecutionRoleArn=role,\n", ")\n", "print(f\"Created Model: {create_model_response['ModelArn']}\")" ] }, { "cell_type": "markdown", "id": "6232b6d3", "metadata": {}, "source": [ "### Create Endpoint Configuration\n", "\n", "`ServerlessConfig`으로 엔드포인트에 대한 서버리스 설정을 조정할 수 있습니다. 최대 동시 호출(`MaxConcurrency`; max concurrent invocations)은 1에서 50 사이이며, `MemorySize`는 1024MB, 2048MB, 3072MB, 4096MB, 5120MB 또는 6144MB를 선택할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "eab981df", "metadata": {}, "outputs": [], "source": [ "endpoint_config_name = f\"KorNLPServerlessEndpointConfig-{nlp_task}-{strftime('%Y-%m-%d-%H-%M-%S', gmtime())}\"\n", "endpoint_config_response = sm_client.create_endpoint_config(\n", " EndpointConfigName=endpoint_config_name,\n", " ProductionVariants=[\n", " {\n", " \"VariantName\": \"AllTraffic\",\n", " \"ModelName\": model_name,\n", " \"ServerlessConfig\": {\n", " \"MemorySizeInMB\": 4096,\n", " \"MaxConcurrency\": 20,\n", " }, \n", " },\n", " ],\n", ")\n", "print(f\"Created EndpointConfig: {endpoint_config_response['EndpointConfigArn']}\")" ] }, { "cell_type": "markdown", "id": "34271860", "metadata": {}, "source": [ "### Create a SageMaker Multi-container endpoint\n", "\n", "create_endpoint API로 멀티 컨테이너 엔드포인트를 생성합니다. 기존의 엔드포인트 생성 방법과 동일합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "d93e4b6f", "metadata": {}, "outputs": [], "source": [ "endpoint_name = f\"KorNLPServerlessEndpoint-{nlp_task}-{strftime('%Y-%m-%d-%H-%M-%S', gmtime())}\"\n", "endpoint_response = sm_client.create_endpoint(\n", " EndpointName=endpoint_name, \n", " EndpointConfigName=endpoint_config_name\n", ")\n", "print(f\"Creating Endpoint: {endpoint_response['EndpointArn']}\")" ] }, { "cell_type": "markdown", "id": "1d435bd7", "metadata": {}, "source": [ "`describe_endpoint` API를 사용하여 엔드포인트 생성 상태를 확인할 수 있습니다. SageMaker 서버리스 엔드포인트는 일반적인 엔드포인트 생성보다 빠르게 생성됩니다. (약 2-3분)" ] }, { "cell_type": "code", "execution_count": null, "id": "f9288c7e", "metadata": {}, "outputs": [], "source": [ "%%time\n", "waiter = boto3.client('sagemaker').get_waiter('endpoint_in_service')\n", "print(\"Waiting for endpoint to create...\")\n", "waiter.wait(EndpointName=endpoint_name)\n", "resp = sm_client.describe_endpoint(EndpointName=endpoint_name)\n", "print(f\"Endpoint Status: {resp['EndpointStatus']}\")" ] }, { "cell_type": "markdown", "id": "f0694a36", "metadata": {}, "source": [ "### Direct Invocation for Model \n", "\n", "최초 호출 시 Cold start로 지연 시간이 발생하지만, 최초 호출 이후에는 warm 상태를 유지하기 때문에 빠르게 응답합니다. 물론 수 분 동안 호출이 되지 않거나 요청이 많아지면 cold 상태로 바뀐다는 점을 유의해 주세요." ] }, { "cell_type": "code", "execution_count": null, "id": "9610286f", "metadata": {}, "outputs": [], "source": [ "model_sample_path = 'samples/nsmc.txt'\n", "!cat $model_sample_path\n", "with open(model_sample_path, mode='rb') as file:\n", " model_input_data = file.read() \n", "\n", "model_response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/jsonlines\",\n", " Accept=\"application/jsonlines\",\n", " Body=model_input_data\n", ")\n", "\n", "model_outputs = model_response['Body'].read().decode()\n", "print()\n", "print_outputs(model_outputs)" ] }, { "cell_type": "markdown", "id": "57441617", "metadata": {}, "source": [ "### Check Model Latency" ] }, { "cell_type": "code", "execution_count": null, "id": "a61db0d0", "metadata": {}, "outputs": [], "source": [ "import time\n", "start = time.time()\n", "for _ in range(10):\n", " model_response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/jsonlines\",\n", " Accept=\"application/jsonlines\",\n", " Body=model_input_data\n", ")\n", "inference_time = (time.time()-start)\n", "print(f'Inference time is {inference_time:.4f} ms.')" ] }, { "cell_type": "markdown", "id": "0645b4d2", "metadata": {}, "source": [ "
\n", "\n", "## Clean Up\n", "---" ] }, { "cell_type": "code", "execution_count": null, "id": "da123982", "metadata": { "scrolled": true }, "outputs": [], "source": [ "sm_client.delete_endpoint(EndpointName=endpoint_name)\n", "sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name)\n", "sm_client.delete_model(ModelName=model_name)" ] } ], "metadata": { "kernelspec": { "display_name": "conda_pytorch_latest_p37", "language": "python", "name": "conda_pytorch_latest_p37" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" } }, "nbformat": 4, "nbformat_minor": 5 }