{ "cells": [ { "cell_type": "markdown", "id": "b9fc0af1", "metadata": {}, "source": [ "# Deploy Hugging Face Transformers in SageMaker Real-time Endpoint\n", "---" ] }, { "cell_type": "markdown", "id": "1e3ae707", "metadata": {}, "source": [ "## Introduction\n", "---\n", "\n", "본 모듈에서는 Hugging Face 모델을 리얼타임 엔드포인트로 배포합니다. SageMakers는 사전 빌드된 Hugging Face 추론 컨테이너와 Hugging Face Inference Toolkit을 제공하고 있기 때문에, 기존 SageMaker 엔드포인트 배포와 동일한 방법으로 진행할 수 있습니다. 또한, Hugging Face 전용 기능으로 Hugging Face Hub(https://huggingface.co/models) 에 등록된 모델을 직접 임포트해서 엔드포인트 배포가 가능합니다. 아래의 예제 코드를 참조해 주세요.\n", "\n", "```python\n", "hub = {\n", " 'HF_MODEL_ID': model_id, \n", " 'HF_TASK':'text-classification' \n", "}\n", "\n", "hf_hub_model = HuggingFaceModel(\n", " env=hub,\n", " ...\n", ")\n", "```\n", "\n", "SageMaker Hugging Face Inference Toolkit은 ML 모델을 제공하기 위해 [멀티 모델 서버(MMS; Multi Model Server)](https://github.com/awslabs/multi-model-server)를 사용합니다. SageMaker와 호환되도록 하는 구성 및 설정으로 MMS를 부트스트랩하고 시나리오의 요구 사항에 따라 모델 당 작업자 수(number of workers per model)와 같은 중요한 성능 매개변수를 조정할 수 있습니다.\n", "\n", "보다 다양한 유즈케이스에 대한 예제 코드가 필요하고 핸즈온 및 추론에 필요한 스크립트를 커스터마이징하고 싶다면(BYOS; Bring Your Own Scripts) 아래 URL을 참조하세요.\n", "\n", "- SageMaker Hugging Face Inference Toolkit: https://github.com/aws/sagemaker-huggingface-inference-toolkit\n", "- Amazon SageMaker Deep Learning Inference Hands-on-Lab: https://github.com/aws-samples/sagemaker-inference-samples-kr\n", "\n", "\n", "엔드포인트 생성은 다음의 세 단계로 구성됩니다.\n", "1. **모델(Model) 생성** — SageMaker 배포에 필요한 모델을 생성합니다. 추론 컨테이너 이미지와 모델 아티팩트의 S3 경로를 설정합니다.\n", "2. **엔드포인트 설정(Endpoint Configuration) 생성** — 프로덕션 변형(production variants)에서 하나 이상의 모델 이름과 SageMaker가 각 프로덕션 변형을 호스팅하기 위해 프로비저닝할 추론 호스팅 인스턴스 타입을 지정합니다.\n", "3. **엔드포인트(Endpoint) 생성** — 엔드포인트 설정을 기반으로 엔드포인트를 생성합니다. 호스팅 인스턴스를 프로비저닝하고 모델을 배포합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "66f3db2c", "metadata": {}, "outputs": [], "source": [ "import os\n", "import json\n", "import sys\n", "import logging\n", "import boto3\n", "import sagemaker\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", "sess = sagemaker.Session()\n", "role = sagemaker.get_execution_role()\n", "region = boto3.Session().region_name" ] }, { "cell_type": "markdown", "id": "fd05a816", "metadata": {}, "source": [ "
\n", "\n", "## 1. [Option 1] Deploy a trained model from Amazon S3\n", "---\n", "\n", "사전 훈련된 모델 아티팩트를 곧바로 배포하는 것은 물론(Option 1), Hugging Face Hub로부터 모델을 직접 복사하는 방법(Option 2)도 가능합니다. " ] }, { "cell_type": "code", "execution_count": null, "id": "7d7a15d0", "metadata": {}, "outputs": [], "source": [ "model_dir = 'model'\n", "!rm -rf {model_dir}\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": "37ab3790", "metadata": {}, "source": [ "모델 파라메터 및 토크나이저를 `model.tar.gz`으로 압축합니다. 압축 파일명은 자유롭게 지정할 수 있으나, 반드시 `tar.gz`로 압축해야 합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "f80b6dde", "metadata": {}, "outputs": [], "source": [ "model_artifact_name = 'model.tar.gz'\n", "!cd model && tar -czvf {model_artifact_name} *.*" ] }, { "cell_type": "markdown", "id": "eae11e84", "metadata": {}, "source": [ "압축한 모델 아티팩트를 Amazon S3로 복사합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "c6c32586", "metadata": {}, "outputs": [], "source": [ "s3_prefix = 'samples/models/nsmc'\n", "s3_model_path = f's3://{sess.default_bucket()}/{s3_prefix}'\n", "!aws s3 cp {model_dir}/{model_artifact_name} {s3_model_path}/{model_artifact_name}" ] }, { "cell_type": "markdown", "id": "b94bd00a", "metadata": {}, "source": [ "### [SageMaker Notebook Only] Local Mode Deployment\n", "SageMaker 호스팅 엔드포인트로 배포하기 전에 로컬 모드 엔드포인트로 배포할 수 있습니다. 로컬 모드는 현재 개발 중인 환경에서 도커 컨테이너를 실행하여 SageMaker 프로세싱/훈련/추론 작업을 에뮬레이트할 수 있습니다. 추론 작업의 경우는 Amazon ECR의 딥러닝 프레임워크 기반 추론 컨테이너를 로컬로 가져오고(docker pull) 컨테이너를 실행하여(docker run) 모델 서버를 시작합니다.\n", "\n", "내부적으로 아래와 같은 과정으로 테스트를 직접 수행할 수 있습니다.\n", "\n", "```python\n", "\n", "local_model_path = f'{os.getcwd()}/model'\n", "ecr_uri = f'763104351884.dkr.ecr.{region}.amazonaws.com/huggingface-pytorch-inference:1.9.1-transformers4.12.3-cpu-py38-ubuntu20.04'\n", "\n", "# 도커 컨테이너 구동\n", "!docker run --name hf -itd -p 8080:8080 -v {local_model_path}:/opt/ml/model {ecr_uri} serve\n", " \n", "# 실시간 호출 테스트 \n", "!curl -X POST -H 'Content-Type: application/json' localhost:8080/invocations -d '{\"inputs\": [\"불후의 명작입니다\"]}' \n", "\n", "# 도커 컨테이너 중지 및 삭제 \n", "!docker stop hf\n", "!docker rm hf \n", "\n", "```\n", "\n", "참고로 SageMaker SDK에서 `deploy(...)` 메소드로 엔드포인트 배포 시, 인스턴스 타입을 `local` 이나 `local_gpu`로 지정하면 위의 과정을 자동으로 수행할 수 있습니다. \n", " \n", "```python\n", "# 로컬 엔드포인트 배포\n", "hf_predictor_local = hf_model.deploy(initial_instance_count=1, instance_type=\"local\")\n", "\n", "# 실시간 호출 테스트 \n", "hf_predictor_local.predict({\"inputs\": [\"불후의 명작입니다\"]})\n", "\n", "# 로컬 엔드포인트 삭제 (도커 컨테이너 중지 및 삭제)\n", "hf_predictor_local.delete_endpoint()\n", "```" ] }, { "cell_type": "markdown", "id": "a72ecf6a", "metadata": {}, "source": [ "### SageMaker Endpoint Deployment\n", " \n", "아래 코드를 보시면 아시겠지만, 지속적으로 업데이트되는 파이썬 버전&프레임워크 버전&트랜스포머 버전에 쉽게 대응할 수 있습니다. AWS에서 관리하고 있는 딥러닝 컨테이너(DLC) 목록을 아래 주소에서 확인해 보세요.\n", "\n", "- https://github.com/aws/deep-learning-containers/blob/master/available_images.md" ] }, { "cell_type": "code", "execution_count": null, "id": "fe1a3d18", "metadata": {}, "outputs": [], "source": [ "# create Hugging Face Model Class\n", "hf_model = HuggingFaceModel(\n", " model_data=f\"{s3_model_path}/{model_artifact_name}\", # path to your trained SageMaker model\n", " role=role, # IAM role with permissions to create an endpoint\n", " transformers_version=\"4.12.3\", # Transformers version used\n", " pytorch_version=\"1.9.1\", # PyTorch version used\n", " py_version='py38', # Python version used\n", ")\n", "\n", "# deploy model to SageMaker Inference\n", "hf_predictor = hf_model.deploy(\n", " initial_instance_count=1,\n", " instance_type=\"ml.m5.xlarge\",\n", " wait=False\n", ")" ] }, { "cell_type": "markdown", "id": "389a6897", "metadata": {}, "source": [ "
\n", "\n", "## 2. [Option 2] Deploy a trained model from Hugging Face Hub\n", "\n", "---\n", "\n", "이 기능은 SageMaker의 Hugging Face 추론 컨테이너에서만 고유하게 지원되는 기능으로, 2개의 환경 변수 정의만으로 Hugging Face Hub에서 SageMaker로 직접 모델을 배포할 수 있습니다.\n", "\n", "- `HF_MODEL_ID`: SageMaker Endpoint 생성 시 Hugging Face Model Hub (http://huggingface.co/models) 에서 자동으로 로드될 모델 ID를 정의합니다. 이를 통해 전세계에 등록된 1만여 가지 이상의 모델을 가져올 수 있습니다.\n", "- `HF_TASK`: 트랜스포머 파이프라인에 대한 다운스트림 작업(task) 명입니다. 작업 리스트는 https://huggingface.co/transformers/main_classes/pipelines.html 를 참조해 주세요.\n", "\n", "_[주의] Hub에서 모델을 임포트하는 기능은 아직 실험적인 기능이라 모델 사이즈가 너무 크면 (예: 10GB 초과) 오류가 발생할 수 있고, 멀티 모델 엔드포인트(Multi-Model Endpoint) 기능을 지원하지 않습니다._" ] }, { "cell_type": "code", "execution_count": null, "id": "74e85d62", "metadata": {}, "outputs": [], "source": [ "model = ElectraForSequenceClassification.from_pretrained(model_id)\n", "hub = {\n", " 'HF_MODEL_ID': model_id, \n", " 'HF_TASK':'text-classification' \n", "}\n", "\n", "# create Hugging Face Model Class\n", "hf_hub_model = HuggingFaceModel(\n", " env=hub,\n", " role=role, # iam role with permissions to create an Endpoint\n", " transformers_version=\"4.12.3\", # transformers version used\n", " pytorch_version=\"1.9.1\", # pytorch version used\n", " py_version=\"py38\", # python version of the DLC\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "c562bb94", "metadata": {}, "outputs": [], "source": [ "# deploy model to SageMaker Inference\n", "hf_hub_predictor = hf_hub_model.deploy(\n", " initial_instance_count=1,\n", " instance_type=\"ml.m5.xlarge\",\n", " wait=False\n", ")" ] }, { "cell_type": "markdown", "id": "662d1e07", "metadata": {}, "source": [ "### Wait for the endpoint jobs to complete\n", "\n", "엔드포인트가 생성될 때까지 기다립니다. 약 5-10분의 시간이 소요됩니다." ] }, { "cell_type": "code", "execution_count": null, "id": "51f16608", "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import display, HTML\n", "\n", "def make_endpoint_link(region, endpoint_name, endpoint_task):\n", " \n", " endpoint_link = f'{endpoint_task} Review Endpoint' \n", " return endpoint_link \n", " \n", "endpoint_link1 = make_endpoint_link(region, hf_predictor.endpoint_name, '[Deploy model from S3]')\n", "endpoint_link2 = make_endpoint_link(region, hf_hub_predictor.endpoint_name, '[Deploy model from Hugging Face Hub]')\n", "\n", "display(HTML(endpoint_link1))\n", "display(HTML(endpoint_link2))" ] }, { "cell_type": "code", "execution_count": null, "id": "297e9d92", "metadata": {}, "outputs": [], "source": [ "sess.wait_for_endpoint(hf_predictor.endpoint_name, poll=5)" ] }, { "cell_type": "code", "execution_count": null, "id": "42439765", "metadata": {}, "outputs": [], "source": [ "sess.wait_for_endpoint(hf_hub_predictor.endpoint_name, poll=5)" ] }, { "cell_type": "markdown", "id": "58701af0", "metadata": {}, "source": [ "
\n", "\n", "## 3. Prediction\n", "\n", "---\n", "\n", "두 개의 엔드포인트가 배포되었습니다. 샘플 데이터로 직접 추론을 수행해 봅니다." ] }, { "cell_type": "code", "execution_count": null, "id": "89ef3417", "metadata": {}, "outputs": [], "source": [ "# example request, you always need to define \"inputs\"\n", "data = {\n", " \"inputs\": [\n", " \"정말 재미있습니다. 세 번 봐도 질리지 않아요.\",\n", " \"시간이 아깝습니다. 다른 영화를 보세요.\"\n", " ]\n", "}" ] }, { "cell_type": "code", "execution_count": null, "id": "0c2bd61a", "metadata": {}, "outputs": [], "source": [ "hf_predictor.predict(data)" ] }, { "cell_type": "code", "execution_count": null, "id": "3e7875c4", "metadata": {}, "outputs": [], "source": [ "hf_hub_predictor.predict(data)" ] }, { "cell_type": "markdown", "id": "e7e86d1f", "metadata": {}, "source": [ "
\n", "\n", "## 4. (Optional) Auto Scaling\n", "---\n", "\n", "SageMaker 자동 스케일링은 추론 워크로드를 모니터링하고 용량을 동적으로 조정하여 가능한 최저 비용으로 안정적인 추론 서비스를 가능하게 합니다. 자세한 내용은 아래 블로그 포스트와 개발자 가이드를 참조하세요.\n", "\n", "- Configuring autoscaling inference endpoints in Amazon SageMaker: https://aws.amazon.com/de/blogs/machine-learning/configuring-autoscaling-inference-endpoints-in-amazon-sagemaker/\n", "- https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-target-tracking.html" ] }, { "cell_type": "markdown", "id": "7065dc48", "metadata": {}, "source": [ "### Configure Autoscaling for our Endpoint\n", "\n", "엔드포인트당 최소(minimum), 원하는(desired) 및 최대(maximum 인스턴스 수를 정의할 수 있으며, 자동 스케일링 구성을 기반으로 인스턴스가 동적으로 관리됩니다. 본 핸즈온에서는 가장 기본적인 `SageMakerVariantInvocationsPerInstance`를 사용합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "e23e0e35", "metadata": {}, "outputs": [], "source": [ "endpoint_name = hf_predictor.endpoint_name" ] }, { "cell_type": "code", "execution_count": null, "id": "01422b23", "metadata": {}, "outputs": [], "source": [ "import boto3\n", "\n", "# Let us define a client to play with autoscaling options\n", "asg_client = boto3.client('application-autoscaling') \n", "\n", "# here resource type is variant and the unique identifier is the resource ID.\n", "# Example: endpoint/my-bert-fine-tuned/variant/AllTraffic .\n", "resource_id = f\"endpoint/{endpoint_name}/variant/AllTraffic\"\n", "\n", "# scaling configuration\n", "response = asg_client.register_scalable_target(\n", " ServiceNamespace='sagemaker',\n", " ResourceId=resource_id,\n", " ScalableDimension='sagemaker:variant:DesiredInstanceCount', \n", " MinCapacity=1,\n", " MaxCapacity=4\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "7c72e550", "metadata": {}, "outputs": [], "source": [ "response = asg_client.put_scaling_policy(\n", " PolicyName=f'Request-ScalingPolicy-{endpoint_name}',\n", " ServiceNamespace='sagemaker',\n", " ResourceId=resource_id,\n", " ScalableDimension='sagemaker:variant:DesiredInstanceCount',\n", " PolicyType='TargetTrackingScaling',\n", " TargetTrackingScalingPolicyConfiguration={\n", " 'TargetValue': 10.0, # Threshold\n", " 'PredefinedMetricSpecification': {\n", " 'PredefinedMetricType': 'SageMakerVariantInvocationsPerInstance',\n", " },\n", " 'ScaleInCooldown': 300, # duration until scale in\n", " 'ScaleOutCooldown': 60 # duration between scale out\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "98b5d4f4", "metadata": {}, "source": [ "### Stress Test\n", "\n", "본 예제에서는 단순하게 for 루프로 테스트했지만, 좀 더 엄밀하게 테스트하고 싶다면 Lambda를 활용하거나 서드파티 툴킷(예: locust)을 사용하는 것을 권장합니다. " ] }, { "cell_type": "code", "execution_count": null, "id": "6aba9c79", "metadata": {}, "outputs": [], "source": [ "import time\n", "\n", "request_duration_in_seconds = 4*65\n", "end_time = time.time() + request_duration_in_seconds\n", "print(f\"Test will run {request_duration_in_seconds} seconds.\")\n", "\n", "while time.time() < end_time:\n", " hf_predictor.predict(data)" ] }, { "cell_type": "code", "execution_count": null, "id": "6a238e53", "metadata": {}, "outputs": [], "source": [ "boto_session = boto3.session.Session()\n", "region = boto_session.region_name" ] }, { "cell_type": "markdown", "id": "daea2765", "metadata": {}, "source": [ "### Monitoring\n", "\n", "아래 코드 셀에서 출력되는 링크를 클릭해면 CloudWatch 대시보드로 이동합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "83a599c9", "metadata": {}, "outputs": [], "source": [ "print(f\"https://console.aws.amazon.com/cloudwatch/home?region={region}#metricsV2:graph=~(metrics~(~(~'AWS*2fSageMaker~'InvocationsPerInstance~'EndpointName~'{endpoint_name}~'VariantName~'AllTraffic))~view~'timeSeries~stacked~false~region~'{region}~start~'-PT15M~end~'P0D~stat~'SampleCount~period~60);query=~'*7bAWS*2fSageMaker*2cEndpointName*2cVariantName*7d*20{endpoint_name}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "5c273f77", "metadata": {}, "outputs": [], "source": [ "sm_client = boto3.client('sagemaker')\n", "response = sm_client.describe_endpoint(EndpointName=endpoint_name)" ] }, { "cell_type": "markdown", "id": "50350d69", "metadata": {}, "source": [ "트래픽에 따라 인스턴스 개수가 자동으로 조정됩니다. 예를 들어, 몇 분이 지난 다음 다시 확인하면 인스턴스 개수가 4개에서 2개로 조정됩니다." ] }, { "cell_type": "code", "execution_count": null, "id": "13c8f6ab", "metadata": {}, "outputs": [], "source": [ "print(f\"Endpoint {response['EndpointName']} has \\nCurrent Instance Count: {response['ProductionVariants'][0]['CurrentInstanceCount']}\\nWith a desired instance count of {response['ProductionVariants'][0]['DesiredInstanceCount']}\")" ] }, { "cell_type": "markdown", "id": "3d9669b3", "metadata": {}, "source": [ "
\n", "\n", "## Clean up\n", "---\n", "\n", "엔드포인트에 대해 자동 스케일링을 활성화한 경우, 엔드포인트를 삭제하기 전 `deregister_scalable_target()`로 확장 가능한 대상을 해지해야 합니다." ] }, { "cell_type": "code", "execution_count": null, "id": "9565bf44", "metadata": {}, "outputs": [], "source": [ "response = asg_client.deregister_scalable_target(\n", " ServiceNamespace='sagemaker',\n", " ResourceId=resource_id,\n", " ScalableDimension='sagemaker:variant:DesiredInstanceCount'\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "8af0b571", "metadata": {}, "outputs": [], "source": [ "hf_predictor.delete_endpoint()\n", "hf_hub_predictor.delete_endpoint()" ] } ], "metadata": { "interpreter": { "hash": "c281c456f1b8161c8906f4af2c08ed2c40c50136979eaae69688b01f70e9f4a9" }, "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 }