{ "cells": [ { "cell_type": "markdown", "id": "057c8631", "metadata": {}, "source": [ "# Module 2. Deploy Multi-container Endpoint\n", "---\n", "\n", "## Overview\n", "\n", "SageMaker 멀티 컨테이너 엔드포인트를 사용하면 서로 다른 serving 스택(예: 모델 서버, 머신 러닝 프레임워크, 프레임워크 버전, 알고리즘 등)에 구축된 여러 추론 컨테이너를 하나의 엔드포인트에서 실행하고 독립적으로 각 추론 컨테이너를 호출할 수 있습니다. \n", "\n", "- 인스턴스의 전체 수용량을 포화시킬 정도의 트래픽이 없는 경우에 여러 모델(예: Object Detection, Named Entity Recognition)을 서빙\n", "- A/B 테스트와 같은 시나리오에서 서로 다른 프레임워크 버전(예: TensorFlow 1.x vs. TensorFlow 2.x)에서 실행되는 유사한 아키텍처의 비교" ] }, { "cell_type": "markdown", "id": "a2d86db0", "metadata": {}, "source": [ "
\n", "\n", "## 1. Upload Model Artifacts\n", "---\n", "\n", "모델을 아카이빙하여 S3로 업로드합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "2cfcb20f", "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 = 'multi-container-nlp'\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": "ca4c93a7", "metadata": {}, "outputs": [], "source": [ "modelA_variant = 'modelA'\n", "modelA_path = 'model-nsmc'\n", "modelA_s3_uri = upload_model_artifact_to_s3(modelA_variant, modelA_path, bucket, prefix)" ] }, { "cell_type": "code", "execution_count": null, "id": "461fea3a", "metadata": {}, "outputs": [], "source": [ "modelB_variant = 'modelB'\n", "modelB_path = 'model-korsts'\n", "modelB_s3_uri = upload_model_artifact_to_s3(modelB_variant, modelB_path, bucket, prefix)" ] }, { "cell_type": "code", "execution_count": null, "id": "c26f015b", "metadata": {}, "outputs": [], "source": [ "modelC_variant = 'modelC'\n", "modelC_path = 'model-kobart'\n", "modelC_s3_uri = upload_model_artifact_to_s3(modelC_variant, modelC_path, bucket, prefix)" ] }, { "cell_type": "markdown", "id": "0abac512", "metadata": {}, "source": [ "
\n", "\n", "## 2. Create Multi-container endpoint\n", "---\n", "\n", "SageMaker 멀티 컨테이너 엔드포인트를 사용하면 여러 컨테이너들을 동일한 엔드포인트에 배포할 수 있으며, 각 컨테이너에 개별적으로 액세스하여 비용을 최적화할 수 있습니다. 다중 컨테이너 엔드포인트 설정은 기존 엔드포인트와 유사하지만, SageMaker 모델 생성 시 여러 컨테이너들을 명시해 줘야 합니다.\n", "\n", "- 배포에 필요한 각 컨테이너에 대한 추론 컨테이너 정의 생성\n", "- `create_model` API를 사용하여 SageMaker 모델 생성; PrimaryContainer 대신 Containers 매개변수를 사용하고 Containers 매개변수에 두 개 이상의 컨테이너를 포함합니다. (최대 15개까지 지원)\n", "- `create_endpoint_config` API를 사용하여 SageMaker 엔드포인트 설정 생성\n", "- `create_endpoint` API를 사용하여 SageMaker 엔드포인트 생성; 생성 시 반드시 Direct 모드를 사용해야 합니다.\n", "\n", "자세한 내용은 아래 링크를 참조해 주세요.\n", "- Host Multiple Models with Multi-Model Endpoints: https://docs.aws.amazon.com/sagemaker/latest/dg/multi-model-endpoints.html" ] }, { "cell_type": "markdown", "id": "6b7de177", "metadata": {}, "source": [ "### Create Inference containter definition for Model A" ] }, { "cell_type": "code", "execution_count": null, "id": "3b3ae708", "metadata": {}, "outputs": [], "source": [ "from sagemaker.image_uris import retrieve\n", "\n", "deploy_instance_type = 'ml.c5.xlarge'\n", "pt_ecr_image_uriA = 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", ")\n", "\n", "pt_containerA = {\n", " \"ContainerHostname\": \"pytorch-kornlp-nsmc\",\n", " \"Image\": pt_ecr_image_uriA,\n", " \"ModelDataUrl\": modelA_s3_uri,\n", " \"Environment\": {\n", " \"SAGEMAKER_PROGRAM\": \"inference_nsmc.py\",\n", " \"SAGEMAKER_SUBMIT_DIRECTORY\": modelA_s3_uri,\n", " },\n", "}" ] }, { "cell_type": "markdown", "id": "3446e7f8", "metadata": {}, "source": [ "### Create Inference containter definition for Model B" ] }, { "cell_type": "code", "execution_count": null, "id": "1aa16444", "metadata": {}, "outputs": [], "source": [ "pt_ecr_image_uriB = retrieve(\n", " framework='pytorch',\n", " region=region,\n", " version='1.8.1',\n", " py_version='py3',\n", " instance_type = deploy_instance_type,\n", " accelerator_type=None,\n", " image_scope='inference'\n", ")\n", "\n", "pt_containerB = {\n", " \"ContainerHostname\": \"pytorch-kornlp-korsts\",\n", " \"Image\": pt_ecr_image_uriB,\n", " \"ModelDataUrl\": modelB_s3_uri,\n", " \"Environment\": {\n", " \"SAGEMAKER_PROGRAM\": \"inference_korsts.py\",\n", " \"SAGEMAKER_SUBMIT_DIRECTORY\": modelB_s3_uri,\n", " },\n", "}" ] }, { "cell_type": "markdown", "id": "7d9a9483", "metadata": {}, "source": [ "### Create Inference containter definition for Model C" ] }, { "cell_type": "code", "execution_count": null, "id": "0f4485b5", "metadata": {}, "outputs": [], "source": [ "pt_ecr_image_uriC = retrieve(\n", " framework='pytorch',\n", " region=region,\n", " version='1.8.1',\n", " py_version='py3',\n", " instance_type = deploy_instance_type,\n", " accelerator_type=None,\n", " image_scope='inference'\n", ")\n", "\n", "pt_containerC = {\n", " \"ContainerHostname\": \"pytorch-kornlp-kobart\",\n", " \"Image\": pt_ecr_image_uriC,\n", " \"ModelDataUrl\": modelC_s3_uri,\n", " \"Environment\": {\n", " \"SAGEMAKER_PROGRAM\": \"inference_kobart.py\",\n", " \"SAGEMAKER_SUBMIT_DIRECTORY\": modelC_s3_uri,\n", " },\n", "}" ] }, { "cell_type": "markdown", "id": "a125c4cf", "metadata": {}, "source": [ "### Create a SageMaker Model\n", "\n", "`create_model` API를 호출하여 위 코드 셀에서 생성한 다중 컨테이너의 정의를 포함하는 모델을 생성합니다. 기존 엔드포인트와의 차이점은 `Containers` 매개 변수를 설정하고 `InferenceExecutionConfig` 매개변수의 Mode를 `Direct`나 `Serial`로 설정하는 것입니다. 기본 모드는 `Serial`이지만, 각 컨테이너를 직접 호출하려면 `Direct`로 설정해야 합니다. 자세한 내용은 [멀티 컨테이너 엔드포인트 배포](https://docs.aws.amazon.com/sagemaker/latest/dg/multi-container-endpoints.html)를 확인하세요." ] }, { "cell_type": "code", "execution_count": null, "id": "ee2c950e", "metadata": {}, "outputs": [], "source": [ "model_name = f\"KorNLPMultiContainer-{strftime('%Y-%m-%d-%H-%M-%S', gmtime())}\"\n", "\n", "create_model_response = sm_client.create_model(\n", " ModelName=model_name,\n", " Containers=[pt_containerA, pt_containerB, pt_containerC],\n", " InferenceExecutionConfig={\"Mode\": \"Direct\"},\n", " ExecutionRoleArn=role,\n", ")\n", "print(f\"Created Model: {create_model_response['ModelArn']}\")" ] }, { "cell_type": "markdown", "id": "5c7c558a", "metadata": {}, "source": [ "### Create Endpoint Configuration" ] }, { "cell_type": "code", "execution_count": null, "id": "37db052e", "metadata": {}, "outputs": [], "source": [ "endpoint_config_name = f\"KorNLPMultiContainerEndpointConfig-{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\": \"prod\",\n", " \"ModelName\": model_name,\n", " \"InitialInstanceCount\": 1,\n", " \"InstanceType\": deploy_instance_type,\n", " },\n", " ],\n", ")\n", "print(f\"Created EndpointConfig: {endpoint_config_response['EndpointConfigArn']}\")" ] }, { "cell_type": "markdown", "id": "e82aae26", "metadata": {}, "source": [ "### Create a SageMaker Multi-container endpoint\n", "\n", "create_endpoint API로 멀티 컨테이너 엔드포인트를 생성합니다. 기존의 엔드포인트 생성 방법과 동일합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "40a8246a", "metadata": {}, "outputs": [], "source": [ "endpoint_name = f\"KorNLPMultiContainerEndpoint-{strftime('%Y-%m-%d-%H-%M-%S', gmtime())}\"\n", "endpoint_response = sm_client.create_endpoint(\n", " EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name\n", ")\n", "print(f\"Creating Endpoint: {endpoint_response['EndpointArn']}\")" ] }, { "cell_type": "markdown", "id": "d687e54e", "metadata": {}, "source": [ "`describe_endpoint` API를 사용하여 엔드포인트 생성 상태를 확인할 수 있습니다. 엔드포인트 생성은 약 5분에서 10분이 소요됩니다." ] }, { "cell_type": "code", "execution_count": null, "id": "eabb724b", "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": "30e5e5b7", "metadata": {}, "source": [ "### Direct Invocation for Model A\n", "\n", "두 문장간의 유사도를 정량화하는 예시입니다.\n", "- KorNLI and KorSTS: https://github.com/kakaobrain/KorNLUDatasets" ] }, { "cell_type": "code", "execution_count": null, "id": "cafb3e21", "metadata": {}, "outputs": [], "source": [ "modelA_sample_path = 'samples/nsmc.txt'\n", "!cat $modelA_sample_path\n", "with open(modelA_sample_path, mode='rb') as file:\n", " modelA_input_data = file.read() \n", "\n", "modelA_response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/jsonlines\",\n", " Accept=\"application/jsonlines\",\n", " TargetContainerHostname=\"pytorch-kornlp-nsmc\",\n", " Body=modelA_input_data\n", ")\n", "\n", "modelA_outputs = modelA_response['Body'].read().decode()\n", "print()\n", "print_outputs(modelA_outputs)" ] }, { "cell_type": "markdown", "id": "024c97ca", "metadata": {}, "source": [ "### Direct Invocation for Model B\n", "\n", "두 문장간의 유사도를 정량화하는 예시입니다.\n", "- KorNLI and KorSTS: https://github.com/kakaobrain/KorNLUDatasets" ] }, { "cell_type": "code", "execution_count": null, "id": "4e0afbce", "metadata": {}, "outputs": [], "source": [ "modelB_sample_path = 'samples/korsts.txt'\n", "!cat $modelB_sample_path\n", "with open(modelB_sample_path, mode='rb') as file:\n", " modelB_input_data = file.read() \n", " \n", "modelB_response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/jsonlines\",\n", " Accept=\"application/jsonlines\",\n", " TargetContainerHostname=\"pytorch-kornlp-korsts\",\n", " Body=modelB_input_data\n", ")\n", "\n", "modelB_outputs = modelB_response['Body'].read().decode()\n", "print()\n", "print_outputs(modelB_outputs)" ] }, { "cell_type": "markdown", "id": "972c22c3", "metadata": {}, "source": [ "### Direct Invocation for Model C\n", "\n", "문서 내용(예: 뉴스 기사)을 요약하는 예시입니다.\n", "\n", "- KoBART: https://github.com/SKT-AI/KoBART\n", "- KoBART Summarization: https://github.com/seujung/KoBART-summarization" ] }, { "cell_type": "code", "execution_count": null, "id": "6b8d6f44", "metadata": {}, "outputs": [], "source": [ "modelC_sample_path = 'samples/kobart.txt'\n", "!cat $modelC_sample_path\n", "with open(modelC_sample_path, mode='rb') as file:\n", " modelC_input_data = file.read() \n", " \n", "modelC_response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/jsonlines\",\n", " Accept=\"application/jsonlines\",\n", " TargetContainerHostname=\"pytorch-kornlp-kobart\",\n", " Body=modelC_input_data\n", ")\n", "\n", "modelC_outputs = modelC_response['Body'].read().decode()\n", "print()\n", "print_outputs(modelC_outputs)" ] }, { "cell_type": "markdown", "id": "8b5fef3e", "metadata": {}, "source": [ "
\n", "\n", "## Clean Up\n", "---" ] }, { "cell_type": "code", "execution_count": null, "id": "0a752ae5", "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 }