{ "cells": [ { "cell_type": "markdown", "id": "6d93deb7", "metadata": {}, "source": [ "# Lab 2-3. Deploy Hugging Face Transformers in SageMaker Serverless Endpoint\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": "0e1f8b3d", "metadata": {}, "source": [ "
\n", "\n", "## 1. Upload Model Artifacts\n", "---\n", "\n", "모델을 아카이빙하여 S3로 업로드합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "b4e58dd0", "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": "64f4b252", "metadata": {}, "outputs": [], "source": [ "# !pip install torch==1.8.1 torchvision==0.9.1" ] }, { "cell_type": "code", "execution_count": null, "id": "cf42b6ac", "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import datetime\n", "import json\n", "import os\n", "import io\n", "import sagemaker\n", "import time\n", "from time import strftime,gmtime\n", "from sagemaker import get_execution_role\n", "from sagemaker.utils import name_from_base\n", "from sagemaker.pytorch import PyTorchModel\n", "from src.utils import print_outputs, upload_model_artifact_to_s3, NLPPredictor \n", "from transformers import ElectraConfig\n", "from transformers import (\n", " ElectraModel, ElectraTokenizer, ElectraForSequenceClassification\n", ")\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": "4e0f48b6", "metadata": {}, "outputs": [], "source": [ "nlp_task = 'nsmc'\n", "model_path = f'model-{nlp_task}'\n", "model_variant = 'modelA'\n", "!rm -rf {model_path}\n", "\n", "# Define the model repo\n", "tokenizer_id = 'monologg/koelectra-small-v3-discriminator'\n", "model_id = \"daekeun-ml/koelectra-small-v3-nsmc\"\n", "\n", "# Download model and tokenizer\n", "model = ElectraForSequenceClassification.from_pretrained(model_id)\n", "tokenizer = ElectraTokenizer.from_pretrained(tokenizer_id)\n", "\n", "os.makedirs(model_path, exist_ok=True)\n", "model.save_pretrained(model_path)\n", "tokenizer.save_pretrained(model_path)" ] }, { "cell_type": "code", "execution_count": null, "id": "53fc2e1c", "metadata": {}, "outputs": [], "source": [ "model_s3_uri = upload_model_artifact_to_s3(model_variant, model_path, bucket, prefix)" ] }, { "cell_type": "markdown", "id": "3bf639e3", "metadata": {}, "source": [ "
\n", "\n", "## 2. Create SageMaker Serverless endpoint\n", "---" ] }, { "cell_type": "markdown", "id": "9cd00ff3", "metadata": {}, "source": [ "### Create Inference containter definition for Model" ] }, { "cell_type": "code", "execution_count": null, "id": "5e791baf", "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.9.1',\n", " py_version='py38',\n", " instance_type = deploy_instance_type,\n", " accelerator_type=None,\n", " image_scope='inference'\n", ")" ] }, { "cell_type": "markdown", "id": "e35c9fb4", "metadata": {}, "source": [ "### Create a SageMaker Model\n", "\n", "`create_model` API를 호출하여 위 코드 셀에서 생성한 컨테이너의 정의를 포함하는 모델을 생성합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "751aac77", "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": "ce820f12", "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": "218c62c0", "metadata": {}, "outputs": [], "source": [ "endpoint_config_name = f\"serverless-endpointconfig-{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": "f2e8c653", "metadata": {}, "source": [ "### Create a SageMaker Multi-container endpoint\n", "\n", "`create_endpoint` API로 멀티 컨테이너 엔드포인트를 생성합니다. 기존의 엔드포인트 생성 방법과 동일합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "18039391", "metadata": {}, "outputs": [], "source": [ "endpoint_name = f\"serverless-endpoint-{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": "fc72a5b9", "metadata": {}, "source": [ "`describe_endpoint` API를 사용하여 엔드포인트 생성 상태를 확인할 수 있습니다. SageMaker 서버리스 엔드포인트는 일반적인 엔드포인트 생성보다 빠르게 생성됩니다. (약 2-3분)" ] }, { "cell_type": "code", "execution_count": null, "id": "50420395", "metadata": {}, "outputs": [], "source": [ "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": "a40f4055", "metadata": {}, "source": [ "### Direct Invocation for Model \n", "\n", "최초 호출 시 Cold start로 지연 시간이 발생하지만, 최초 호출 이후에는 warm 상태를 유지하기 때문에 빠르게 응답합니다. 물론 수 분 동안 호출이 되지 않거나 요청이 많아지면 cold 상태로 바뀐다는 점을 유의해 주세요." ] }, { "cell_type": "code", "execution_count": null, "id": "c77507d1", "metadata": {}, "outputs": [], "source": [ "model_sample_path = 'payload_samples.txt'\n", "with open(model_sample_path, \"w\") as file:\n", " file.write('{\"text\": [\"이 영화는 최고의 영화입니다\"]}\\n')\n", " file.write('{\"text\": [\"최악이에요. 배우의 연기력도 좋지 않고 내용도 너무 허접합니다\"]}') " ] }, { "cell_type": "code", "execution_count": null, "id": "4cc98e83", "metadata": {}, "outputs": [], "source": [ "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": "215011db", "metadata": {}, "source": [ "### Check Model Latency" ] }, { "cell_type": "code", "execution_count": null, "id": "f24f6a02", "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": "c4722b34", "metadata": {}, "source": [ "## Clean Up" ] }, { "cell_type": "code", "execution_count": null, "id": "610f5c8d", "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 }