{ "cells": [ { "cell_type": "markdown", "id": "d3550a59", "metadata": { "papermill": { "duration": 0.018003, "end_time": "2021-06-03T00:09:48.368659", "exception": false, "start_time": "2021-06-03T00:09:48.350656", "status": "completed" }, "tags": [] }, "source": [ "# A/B Test using Production Variants\n", "---\n", "\n", "## Introduction\n", "---\n", "\n", "프로덕션 ML 워크플로에서 데이터 과학자와 머신 러닝 엔지니어는 데이터/모델/컨셉 드리프트에 따른 재훈련, 하이퍼파라메터 튜닝, 피쳐 선택 등과 같은 다양한 방법들을 통해 모델을 개선합니다. 이 때 이전 모델과 신규 모델 간의 A/B 테스트를 수행함으로써, 신규 모델에 대한 검증을 충분히 해야겠죠. 그렇다면 A/B 테스트를 위해 엔드포인트를 재배포하거나 2개의 엔드포인트를 배포해야 할까요? 그렇지 않습니다. 프로덕션 Variant 기능을 사용하면, 각 variant에 대해 동일한 엔드포인트 뒤에서 여러 모델 또는 모델 버전을 테스트할 수 있습니다. \n", "\n", "### Production Variants\n", "프로덕션 Variant로 단일 SageMaker Endpoint에서 신규 모델을 테스트하고 배포할 수 있습니다. 예를 들어, 카나리 롤아웃(canary rollout) 및 블루/그린 배포(blue/green deployment)를 위해 엔드포인트의 모델 간에 트래픽을 이동할 수 있습니다. 물론, 초당 요청 수(requests per second)과 같은 지표를 기반으로 엔드포인트를 자동으로 확장하거나 축소하도록 오토스케일링 policy를 구성할 수도 있습니다.\n", "\n", "본 실습에서는 아래와 같은 기능들을 체험해 봅니다.\n", "- 2개의 프로덕션 variant들을 배포 (Variant1: CPU, Variant2: GPU)\n", "- 트래픽 분포 변경 (50:50 -> 80:20 -> 100:0)\n", "- Variant2 삭제\n", "\n", "\n", "### References\n", "- A/B Testing ML models in production using Amazon SageMaker: https://aws.amazon.com/ko/blogs/machine-learning/a-b-testing-ml-models-in-production-using-amazon-sagemaker/\n", "- Example: https://sagemaker-examples.readthedocs.io/en/latest/sagemaker_endpoints/a_b_testing/a_b_testing.html" ] }, { "cell_type": "code", "execution_count": null, "id": "fd830d44", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "import sys\n", "import logging\n", "import boto3\n", "import sagemaker\n", "import time\n", "from datetime import datetime, timedelta\n", "from sagemaker.huggingface import HuggingFaceModel\n", "from sagemaker import session\n", "from transformers import ElectraConfig\n", "from transformers import (\n", " ElectraModel, ElectraTokenizer, ElectraForSequenceClassification\n", ")\n", "\n", "logging.basicConfig(\n", " level=logging.INFO, \n", " format='[{%(filename)s:%(lineno)d} %(levelname)s - %(message)s',\n", " handlers=[\n", " logging.FileHandler(filename='tmp.log'),\n", " logging.StreamHandler(sys.stdout)\n", " ]\n", ")\n", "logger = logging.getLogger(__name__)\n", "\n", "role = sagemaker.get_execution_role()\n", "region = boto3.Session().region_name\n", "\n", "sess = sagemaker.Session()\n", "sm = boto3.Session().client(\"sagemaker\")\n", "sm_runtime = boto3.Session().client(\"sagemaker-runtime\")" ] }, { "cell_type": "markdown", "id": "195712f4", "metadata": {}, "source": [ "
\n", "\n", "## 1. Deploy Models\n", "---\n", "\n", "사전 훈련된 한국어 자연어 처리 모델(네이버 감성 분류 긍정/부정 판별)을 배포합니다. 편의상 동일한 모델을 2개의 프로덕션 변형에 배포하지만, 다른 모델(예: 다른 하이퍼파라메터로 훈련된 모델)을 배포할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "1bc408db", "metadata": {}, "outputs": [], "source": [ "model_dir = 'model'\n", "\n", "# Define the model repo\n", "tokenizer_id = 'daekeun-ml/koelectra-small-v3-nsmc'\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_dir, exist_ok=True)\n", "model.save_pretrained(model_dir)\n", "tokenizer.save_pretrained(model_dir)" ] }, { "cell_type": "markdown", "id": "c2616033", "metadata": {}, "source": [ "모델 파라메터 및 토크나이저를 `model.tar.gz`으로 압축합니다. 압축 파일명은 자유롭게 지정할 수 있으나, 반드시 `tar.gz`로 압축해야 합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "d2abb961", "metadata": {}, "outputs": [], "source": [ "model_artifact_name = 'model.tar.gz'\n", "!cd model && tar -czvf {model_artifact_name} *.*" ] }, { "cell_type": "markdown", "id": "bf08c2d4", "metadata": {}, "source": [ "압축한 모델 아티팩트를 Amazon S3로 복사합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "f5101a6d", "metadata": {}, "outputs": [], "source": [ "s3_prefix = 'ab-test/models/nsmc'\n", "s3_model_path = f's3://{sess.default_bucket()}/{s3_prefix}'\n", "s3_model_url = f'{s3_model_path}/{model_artifact_name}'\n", "!aws s3 cp {model_dir}/{model_artifact_name} {s3_model_path}/{model_artifact_name}" ] }, { "cell_type": "markdown", "id": "b1942ccd", "metadata": {}, "source": [ "### Create Models" ] }, { "cell_type": "code", "execution_count": null, "id": "a0159123", "metadata": {}, "outputs": [], "source": [ "ecr_uri_cpu = f'763104351884.dkr.ecr.{region}.amazonaws.com/huggingface-pytorch-inference:1.9.1-transformers4.12.3-cpu-py38-ubuntu20.04'\n", "ecr_uri_gpu = f'763104351884.dkr.ecr.{region}.amazonaws.com/huggingface-pytorch-inference:1.9.1-transformers4.12.3-gpu-py38-cu111-ubuntu20.04'" ] }, { "cell_type": "code", "execution_count": null, "id": "8855dc17", "metadata": {}, "outputs": [], "source": [ "model_name1 = f\"model-kornlp-nsmc-cpu-{datetime.now():%Y-%m-%d-%H-%M-%S}\"\n", "model_name2 = f\"model-kornlp-nsmc-gpu-{datetime.now():%Y-%m-%d-%H-%M-%S}\"\n", "\n", "sess.create_model(\n", " name=model_name1, role=role, container_defs={\"Image\": ecr_uri_cpu, \"ModelDataUrl\": s3_model_url}\n", ")\n", "\n", "sess.create_model(\n", " name=model_name2, role=role, container_defs={\"Image\": ecr_uri_gpu, \"ModelDataUrl\": s3_model_url}\n", ")" ] }, { "cell_type": "markdown", "id": "a34ffc32", "metadata": {}, "source": [ "### Create Variants\n", "\n", "엔드포인트 설정에서 프로덕션 variant를 여러 개 생성할 수 있습니다. 우선 각 variant에 대해 `initial_weight`를 1로 설정합니다. 즉, 클라이언트 요청의 50%가 Variant1로 이동하고 나머지 50%가 Variant로 이동됨을 의미합니다.\n", "\n", "본 예제에서는 최적의 레이턴시&비용 절충안을 찾기 위해 Variant1을 CPU 인스턴스로 설정하고 Variant2를 GPU 인스턴스로 설정했습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "72999043", "metadata": {}, "outputs": [], "source": [ "from sagemaker.session import production_variant\n", "\n", "variant1 = production_variant(\n", " model_name=model_name1,\n", " instance_type=\"ml.c5.xlarge\",\n", " initial_instance_count=1,\n", " variant_name=\"Variant1\",\n", " initial_weight=1,\n", ")\n", "variant2 = production_variant(\n", " model_name=model_name2,\n", " instance_type=\"ml.g4dn.xlarge\",\n", " initial_instance_count=1,\n", " variant_name=\"Variant2\",\n", " initial_weight=1,\n", ")\n", "(variant1, variant2)" ] }, { "cell_type": "markdown", "id": "7ce051cf", "metadata": {}, "source": [ "### Create Production Variants\n", "\n", "단일 엔드포인트에 2개의 프로덕션 Variant들을 생성합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "18401dba", "metadata": {}, "outputs": [], "source": [ "endpoint_name = f\"endpoint-kornlp-nsmc-{datetime.now():%Y-%m-%d-%H-%M-%S}\"\n", "print(f\"EndpointName={endpoint_name}\")\n", "\n", "sess.endpoint_from_production_variants(\n", " name=endpoint_name, production_variants=[variant1, variant2], wait=False\n", ")" ] }, { "cell_type": "markdown", "id": "8e359809", "metadata": {}, "source": [ "### Wait for the endpoint jobs to complete\n", "\n", "엔드포인트가 생성될 때까지 기다립니다. 약 5-10분의 시간이 소요됩니다. 아래 코드 셀에서 출력되는 AWS 콘솔 링크로 접속해서 엔드포인트 배포 상태를 확인할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "22e56331", "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import display, HTML\n", "\n", "def make_endpoint_link(region, endpoint_name, endpoint_task):\n", " endpoint_link = f'{endpoint_task} Review Endpoint' \n", " return endpoint_link \n", " \n", "endpoint_link = make_endpoint_link(region, endpoint_name, '[Deploy model from S3]')\n", "display(HTML(endpoint_link))" ] }, { "cell_type": "code", "execution_count": null, "id": "dfbd78ab", "metadata": {}, "outputs": [], "source": [ "sess.wait_for_endpoint(endpoint_name, poll=5)" ] }, { "cell_type": "markdown", "id": "4a6e240d", "metadata": {}, "source": [ "
\n", "\n", "## 2. Invoke Endpoint\n", "----\n", "\n", "엔드포인트가 배포되었습니다. 샘플 데이터로 직접 추론을 수행해 봅니다." ] }, { "cell_type": "code", "execution_count": null, "id": "94708ba1", "metadata": {}, "outputs": [], "source": [ "def invoke_endpoint(payload, endpoint_name, target_variant=None):\n", " start = time.time()\n", " if target_variant is not None:\n", " response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/json\", \n", " TargetVariant=target_variant,\n", " Body=payload,\n", " ) \n", " else: \n", " response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/json\", \n", " Body=payload,\n", " )\n", " latency = (time.time() - start) * 1000\n", " variant = response[\"InvokedProductionVariant\"]\n", " logger.info(f'[{variant}] Latency: {latency:.3f} ms')\n", " output = json.loads(response['Body'].read().decode())\n", " return output" ] }, { "cell_type": "code", "execution_count": null, "id": "187a38dc", "metadata": {}, "outputs": [], "source": [ "payload = '{\"inputs\": [\"불후의 명작입니다. 눈물이 앞을 가려요\", \"저런...5점 만점에 1점 주기도 힘들어요.\"]}'\n", "invoke_endpoint(payload, endpoint_name)" ] }, { "cell_type": "markdown", "id": "5ba5940f", "metadata": {}, "source": [ "Variant1와 Variant2가 고르게 호출됨을 확인할 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "c9501920", "metadata": {}, "outputs": [], "source": [ "for i in range(10):\n", " invoke_endpoint(payload, endpoint_name)" ] }, { "cell_type": "code", "execution_count": null, "id": "01e44b89", "metadata": {}, "outputs": [], "source": [ "{\n", " variant[\"VariantName\"]: variant[\"CurrentWeight\"]\n", " for variant in sm.describe_endpoint(EndpointName=endpoint_name)[\"ProductionVariants\"]\n", "}" ] }, { "cell_type": "markdown", "id": "19b91f85", "metadata": {}, "source": [ "`VariantName`으로 Target Variant를 고정적으로 지정할 수도 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "870ec0b7", "metadata": {}, "outputs": [], "source": [ "invoke_endpoint(payload, endpoint_name, variant1['VariantName'])\n", "invoke_endpoint(payload, endpoint_name, variant2['VariantName'])" ] }, { "cell_type": "markdown", "id": "48962388", "metadata": {}, "source": [ "
\n", "\n", "## 3. Update Variant Traffic (Canary Rollouts and A/B Testing)\n", "---\n", "\n", "카나리 롤아웃은 신규 모델을 안전하게 배포하기 위해 사용되는 전략 중 하나입니다. 대분의 트래픽이 기존 모델로 이동하고 카나리 모델의 클러스터에 할당되는 트래픽은 상대적으로 작기 때문에 사용자 경험에 영향을 거의 주지 않습니다. SageMaker에서는 이를 위한 기능을 API로 제공하고 있으며, A/B 테스트 결과에 따라 트래픽을 특정 variant에 더 할당할 경우 굳이 호스팅 엔드포인트를 재배포하실 필요가 없습니다. `UpdateEndpointWeightsAndCapacities`를 사용하면 엔드포인트 중단 없이 각 variant에 할당된 가중치를 쉽게 수정할 수 있기 때문입니다." ] }, { "cell_type": "code", "execution_count": null, "id": "6f3e69f9", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "cw = boto3.Session().client(\"cloudwatch\")\n", "\n", "def get_metrics_for_endpoint_variant(\n", " endpoint_name, \n", " variant_name, \n", " metric_name,\n", " statistic, \n", " start_time, \n", " end_time\n", "):\n", " \n", " dimensions = [\n", " {\"Name\": \"EndpointName\", \"Value\": endpoint_name},\n", " {\"Name\": \"VariantName\", \"Value\": variant_name},\n", " ]\n", "\n", " metrics = cw.get_metric_statistics(\n", " Namespace=\"AWS/SageMaker\",\n", " MetricName=\"Invocations\",\n", " StartTime=start_time,\n", " EndTime=end_time,\n", " Period=60,\n", " Statistics=[statistic],\n", " Dimensions=dimensions\n", " )\n", " return (\n", " pd.DataFrame(metrics[\"Datapoints\"])\n", " .sort_values(\"Timestamp\")\n", " .set_index(\"Timestamp\")\n", " .drop(\"Unit\", axis=1)\n", " .rename(columns={statistic: variant_name}) \n", " )\n", "\n", "\n", "def plot_endpoint_metrics(start_time=None):\n", " start_time = start_time or datetime.now() - timedelta(minutes=60)\n", " end_time = datetime.now()\n", " metric_name = \"Invocations\"\n", " statistic = 'Sum'\n", " metrics_variant1 = get_metrics_for_endpoint_variant(\n", " endpoint_name, variant1[\"VariantName\"], metric_name, statistic, start_time, end_time\n", " )\n", " metrics_variant2 = get_metrics_for_endpoint_variant(\n", " endpoint_name, variant2[\"VariantName\"], metric_name, statistic, start_time, end_time\n", " )\n", " metrics_variants = metrics_variant1.join(metrics_variant2, how=\"outer\")\n", " metrics_variants.plot()\n", " return metrics_variants" ] }, { "cell_type": "markdown", "id": "b2dfbc5a", "metadata": {}, "source": [ "### Variant 트래픽 테스트\n", "\n", "약 2분여간 추론 요청들을 수행하면서 각 variant의 트래픽 분포를 확인해 봅니다. 현재는 50:50 가중치이므로 트래픽 분포가 고르게 이루어지고 있다는 것을 알 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "5a24f443", "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, timedelta\n", "def invoke_endpoint_many(payload, endpoint_name, num_requests=250, sleep_secs=0.5):\n", " for i in range(num_requests):\n", " print(\".\", end=\"\", flush=True)\n", " response = sm_runtime.invoke_endpoint(\n", " EndpointName=endpoint_name,\n", " ContentType=\"application/json\", \n", " Body=payload,\n", " )\n", " output = json.loads(response['Body'].read().decode())\n", " time.sleep(sleep_secs)\n", "\n", "payload = '{\"inputs\": [\"불후의 명작입니다. 눈물이 앞을 가려요\", \"저런...5점 만점에 1점 주기도 힘들어요.\"]}'\n", "invocation_start_time = datetime.now()\n", "invoke_endpoint_many(payload, endpoint_name)\n", "time.sleep(20) # give metrics time to catch up\n", "plot_endpoint_metrics(invocation_start_time)" ] }, { "cell_type": "markdown", "id": "b388e1e8", "metadata": {}, "source": [ "### Variant 가중치 변경 (80:20)\n", "\n", "이제 `UpdateEndpointWeightsAndCapacities`를 사용하여 각 variant의 가중치를 변경합니다. 트래픽의 80%를 variant1로 이동하고 나머지 트래픽을 variant2로 이동합니다. Variant 가중치 수정 후 곧바로 2분 정도 추론 요청을 연속적으로 수행해 보겠습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "ae748dd8", "metadata": {}, "outputs": [], "source": [ "sm.update_endpoint_weights_and_capacities(\n", " EndpointName=endpoint_name,\n", " DesiredWeightsAndCapacities=[\n", " {\"DesiredWeight\": 80, \"VariantName\": variant1[\"VariantName\"]},\n", " {\"DesiredWeight\": 20, \"VariantName\": variant2[\"VariantName\"]},\n", " ],\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "776e3f19", "metadata": {}, "outputs": [], "source": [ "print(\"Waiting for update to complete\")\n", "while True:\n", " status = sm.describe_endpoint(EndpointName=endpoint_name)[\"EndpointStatus\"]\n", " if status in [\"InService\", \"Failed\"]:\n", " print(\"Done\")\n", " break\n", " print(\".\", end=\"\", flush=True)\n", " time.sleep(1)\n", "\n", "{\n", " variant[\"VariantName\"]: variant[\"CurrentWeight\"]\n", " for variant in sm.describe_endpoint(EndpointName=endpoint_name)[\"ProductionVariants\"]\n", "}" ] }, { "cell_type": "markdown", "id": "fe74aa63", "metadata": {}, "source": [ "대부분의 추론 요청이 Variant1에서 처리되고 있으며, Variant2에서 처리된 추론 요청이 적다는 것을 볼 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "95bd6d5e", "metadata": {}, "outputs": [], "source": [ "invoke_endpoint_many(payload, endpoint_name)\n", "time.sleep(20) # give metrics time to catch up\n", "plot_endpoint_metrics(invocation_start_time)" ] }, { "cell_type": "markdown", "id": "0b30e2f2", "metadata": {}, "source": [ "### Variant 가중치 변경 (100:0)\n", "\n", "Variant1의 퍼포먼스가 만족스럽다면 트래픽의 100%를 모두 variant1로 보내도록 라우팅할 수 있습니다. variant 가중치 수정 후 곧바로 2분 정도 추론 요청을 연속적으로 수행해 보겠습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "9fdeb6c3", "metadata": {}, "outputs": [], "source": [ "sm.update_endpoint_weights_and_capacities(\n", " EndpointName=endpoint_name,\n", " DesiredWeightsAndCapacities=[\n", " {\"DesiredWeight\": 1, \"VariantName\": variant1[\"VariantName\"]},\n", " {\"DesiredWeight\": 0, \"VariantName\": variant2[\"VariantName\"]},\n", " ],\n", ")\n", "print(\"Waiting for update to complete\")\n", "while True:\n", " status = sm.describe_endpoint(EndpointName=endpoint_name)[\"EndpointStatus\"]\n", " if status in [\"InService\", \"Failed\"]:\n", " print(\"Done\")\n", " break\n", " print(\".\", end=\"\", flush=True)\n", " time.sleep(1)\n", "\n", "{\n", " variant[\"VariantName\"]: variant[\"CurrentWeight\"]\n", " for variant in sm.describe_endpoint(EndpointName=endpoint_name)[\"ProductionVariants\"]\n", "}" ] }, { "cell_type": "markdown", "id": "b4d1e99a", "metadata": {}, "source": [ "모든 추론 요청이 Variant1에서 처리되고 있으며, Variant2에서 처리된 추론 요청이 없다는 것을 볼 수 있습니다." ] }, { "cell_type": "code", "execution_count": null, "id": "55998f0d", "metadata": {}, "outputs": [], "source": [ "invoke_endpoint_many(payload, endpoint_name)\n", "time.sleep(20) # give metrics time to catch up\n", "plot_endpoint_metrics(invocation_start_time)" ] }, { "cell_type": "markdown", "id": "184baed5", "metadata": {}, "source": [ "이슈가 없다면 곧바로 엔드포인트에서 Variant2를 삭제할 수 있습니다. 바로 아래 섹션에서 Variant2를 삭제해 보겠습니다. 물론, 프로덕션에서 새로운 테스트 환경이 필요할 때에는 엔드포인트에 신규 variant를 추가하고 신규 모델을 계속 테스트할 수 있습니다. " ] }, { "cell_type": "code", "execution_count": null, "id": "c7743479", "metadata": {}, "outputs": [], "source": [ "endpoint_config_name = sm.describe_endpoint(EndpointName=endpoint_name)['EndpointConfigName']" ] }, { "cell_type": "markdown", "id": "dc7fee69", "metadata": {}, "source": [ "
\n", "\n", "## 4. Delete Variant\n", "---\n", "\n", "Variant를 여러 개 띄운다는 것은 모델 호스팅 클러스터를 여러 개 띄운다는 의미입니다. 이제, 불필요한 과금을 피하기 위해 Variant1만 사용하도록 엔드포인트 구성을 업데이트합니다. 엔드포인트 업데이트는 수 분이 소요되지만, 엔드포인트 업데이트 중에도 **다운타임이 발생하지 않는다는 점**을 주목해 주세요. (즉, `invoke_endpoint()`를 계속 수행할 수 있습니다.)\n", "\n", "\n", "**[Tip]** 본 핸즈온에서는 빠른 실습을 위해 곧바로 Variant2의 클러스터를 삭제했지만, 실제 프로덕션에서는 이전 클러스터로 빠르게 롤백해야 하는 경우를 대비하여, Variant2를 일정 시간 동안 유휴 상태로 유지하는 것을 권장드립니다." ] }, { "cell_type": "code", "execution_count": null, "id": "94cb9d74", "metadata": {}, "outputs": [], "source": [ "updated_endpoint_config_name = f\"updated-endpoint-config-kornlp-nsmc-{datetime.now():%Y-%m-%d-%H-%M-%S}\"\n", "print(updated_endpoint_config_name)\n", "\n", "updated_endpoint_config = sm.create_endpoint_config(\n", " EndpointConfigName=updated_endpoint_config_name,\n", " ProductionVariants=[\n", " {\n", " 'VariantName': variant1[\"VariantName\"], # Only specify variant1 to remove variant2\n", " 'ModelName': model_name1,\n", " 'InstanceType':'ml.m5.xlarge',\n", " 'InitialInstanceCount': 1,\n", " 'InitialVariantWeight': 100\n", " }\n", " ])" ] }, { "cell_type": "code", "execution_count": null, "id": "17787fab", "metadata": {}, "outputs": [], "source": [ "sm.update_endpoint(\n", " EndpointName=endpoint_name,\n", " EndpointConfigName=updated_endpoint_config_name\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "6895b9a1", "metadata": {}, "outputs": [], "source": [ "invoke_endpoint(payload, endpoint_name)" ] }, { "cell_type": "markdown", "id": "37128262", "metadata": {}, "source": [ "AWS 콘솔에서 엔드포인트 상태를 확인합니다. Updating 상태로 수 분의 시간이 경과 후 `InServce`로 변경됩니다." ] }, { "cell_type": "code", "execution_count": null, "id": "87d1f840", "metadata": {}, "outputs": [], "source": [ "endpoint_link = make_endpoint_link(region, endpoint_name, '[Deploy model from S3]')\n", "display(HTML(endpoint_link))" ] }, { "cell_type": "code", "execution_count": null, "id": "b2b6bade", "metadata": {}, "outputs": [], "source": [ "sess.wait_for_endpoint(endpoint_name, poll=5)" ] }, { "cell_type": "markdown", "id": "24ee4355", "metadata": {}, "source": [ "
\n", "\n", "## Clean up\n", "---" ] }, { "cell_type": "code", "execution_count": null, "id": "ddfd6bdc", "metadata": {}, "outputs": [], "source": [ "sess.delete_endpoint(endpoint_name)" ] }, { "cell_type": "code", "execution_count": null, "id": "592651e0", "metadata": {}, "outputs": [], "source": [ "sess.delete_endpoint_config(endpoint_config_name)\n", "sess.delete_endpoint_config(updated_endpoint_config_name)" ] }, { "cell_type": "code", "execution_count": null, "id": "e8a5755f", "metadata": {}, "outputs": [], "source": [ "sess.delete_model(model_name1)\n", "sess.delete_model(model_name2)" ] }, { "cell_type": "code", "execution_count": null, "id": "3ddbb7f9", "metadata": {}, "outputs": [], "source": [ "!rm -rf {model_dir}" ] } ], "metadata": { "anaconda-cloud": {}, "instance_type": "ml.t3.medium", "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" }, "notice": "Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the \"License\"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the \"license\" file accompanying this file. This file is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.", "papermill": { "default_parameters": {}, "duration": 199.476853, "end_time": "2021-06-03T00:13:06.967499", "environment_variables": {}, "exception": true, "input_path": "a_b_testing.ipynb", "output_path": "/opt/ml/processing/output/a_b_testing-2021-06-03-00-05-59.ipynb", "parameters": { "kms_key": "arn:aws:kms:us-west-2:521695447989:key/6e9984db-50cf-4c7e-926c-877ec47a8b25" }, "start_time": "2021-06-03T00:09:47.490646", "version": "2.3.3" } }, "nbformat": 4, "nbformat_minor": 5 }