{
"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
}