{
"cells": [
{
"cell_type": "markdown",
"id": "f2c3e0ed",
"metadata": {},
"source": [
"# Module 1. Check Inference Results & Local Mode Deployment\n",
"---\n",
"\n",
"## Overview\n",
"\n",
"본 핸즈온은 AWS AIML Blog의 내용을 기반으로 MNIST 예제 대신 좀 더 실용적인 한국어 자연어 처리 예시를 다루며, 총 3종류(Sentiment Classification, KorSTS, KoBART)의 자연어 처리 모델을 SageMaker 다중 컨테이너 엔드포인트(Multi-container endpoint)로 배포하는 법을 익혀 봅니다.\n",
"\n",
"이미 SageMaker 기본 개념(로컬 모드, 호스팅 엔드포인트)과 자연어 처리 & Huggingface을 다뤄 보신 분들은 이 섹션을 건너 뛰고 다음 노트북으로 진행하셔도 됩니다.\n",
"\n",
"### References\n",
"- AWS AIML Blog: https://aws.amazon.com/ko/blogs/machine-learning/deploy-multiple-serving-containers-on-a-single-instance-using-amazon-sagemaker-multi-container-endpoints/\n",
"- Developer Guide: https://docs.aws.amazon.com/sagemaker/latest/dg/multi-container-endpoints.html"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a28d8eb",
"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": "0defa375",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"import sys\n",
"import torch\n",
"import boto3\n",
"import sagemaker\n",
"import datetime\n",
"from sagemaker import get_execution_role\n",
"from sagemaker.pytorch import PyTorchModel\n",
"from src.utils import print_outputs, prepare_model_artifact, 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\")"
]
},
{
"cell_type": "markdown",
"id": "b0d907e8",
"metadata": {},
"source": [
"
\n",
"\n",
"## 1. Check Inference Results & Debugging\n",
"---\n",
"\n",
"로컬 엔드포인트나 호스팅 엔드포인트 배포 전, 로컬 환경 상에서 직접 추론을 수행하여 결과를 확인합니다. 참고로, SageMaker에서 TensorFlow를 제외한 머신 러닝 프레임워크 추론 컨테이너는 아래의 인터페이스를 사용합니다.\n",
"\n",
"#### Option 1.\n",
"- `model_fn(model_dir)`: 네트워크 아키텍처를 정의하고 S3의 model_dir에 저장된 모델 아티팩트를 로드합니다.\n",
"- `input_fn(request_body, content_type)`: 입력 데이터를 전처리합니다. (예: request_body로 전송된 bytearray 배열을 PIL.Image로 변환 수 cropping, resizing, normalization등의 전처리 수행). content_type은 입력 데이터 종류에 따라 다양하게 처리 가능합니다. (예: application/x-npy, application/json, application/csv 등)\n",
"- `predict_fn(input_object, model)`: input_fn을 통해 들어온 데이터에 대해 추론을 수행합니다.\n",
"- `output_fn(prediction, accept_type)`: predict_fn에서 받은 추론 결과를 추가 변환을 거쳐 프론트 엔드로 전송합니다.\n",
"\n",
"#### Option 2.\n",
"- `model_fn(model_dir)`: 네트워크 아키텍처를 정의하고 S3의 model_dir에 저장된 모델 아티팩트를 로드합니다.\n",
"- `transform_fn(model, request_body, content_type, accept_type)`: input_fn(), predict_fn(), output_fn()을 transform_fn()으로 통합할 수 있습니다.\n",
"\n",
"모델, 배포에 초점을 맞추기 위해 Huggingface에 등록된 `KoELECTRA-Small-v3` 모델을 기반으로 네이버 영화 리뷰 데이터셋과 KorSTS (Korean Semantic Textual Similarity) 데이터셋으로 파인 튜닝하였습니다. 파인 튜닝은 온프레미스나 Huggingface on SageMaker로 쉽게 수행 가능합니다. \n",
"\n",
"- KoELECTRA: https://github.com/monologg/KoELECTRA\n",
"- Huggingface on Amazon SageMaker: https://huggingface.co/docs/sagemaker/main\n",
"\n",
"\n",
"### Model A: Sentiment Classification\n",
"\n",
"네이버 영화 리뷰 데이터의 긍정/부정 판별 예시입니다. \n",
"- Naver sentiment movie corpus: https://github.com/e9t/nsmc\n",
"- 예시\n",
" - '이 영화는 최고의 영화입니다' => {\"predicted_label\": \"Pos\", \"score\": 0.96}\n",
" - '최악이에요. 배우의 연기력도 좋지 않고 내용도 너무 허접합니다' => {\"predicted_label\": \"Neg\", \"score\": 0.99}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1e1d3822",
"metadata": {},
"outputs": [],
"source": [
"!pygmentize src/inference_nsmc.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe27931b",
"metadata": {},
"outputs": [],
"source": [
"from src.inference_nsmc import model_fn, input_fn, predict_fn, output_fn\n",
"modelA_path = 'model-nsmc'\n",
"\n",
"with open('samples/nsmc.txt', mode='rb') as file:\n",
" modelA_input_data = file.read()\n",
"\n",
"modelA = model_fn(modelA_path)\n",
"transformed_inputs = input_fn(modelA_input_data)\n",
"predicted_classes_jsonlines = predict_fn(transformed_inputs, modelA)\n",
"modelA_outputs = output_fn(predicted_classes_jsonlines)\n",
"print(modelA_outputs[0]) "
]
},
{
"cell_type": "markdown",
"id": "1021b735",
"metadata": {},
"source": [
"### Model B: Semantic Textual Similarity (STS)\n",
"\n",
"두 문장간의 유사도를 정량화하는 예시입니다.\n",
"- KorNLI and KorSTS: https://github.com/kakaobrain/KorNLUDatasets\n",
"- 예시\n",
" - ['맛있는 라면을 먹고 싶어요', '후루룩 쩝쩝 후루룩 쩝쩝 맛좋은 라면'] => {\"score\": 4.78}\n",
" - ['뽀로로는 내친구', '머신러닝은 러닝머신이 아닙니다.'] => {\"score\": 0.23}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c41c5ee2",
"metadata": {},
"outputs": [],
"source": [
"!pygmentize src/inference_korsts.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd00c591",
"metadata": {},
"outputs": [],
"source": [
"from src.inference_korsts import model_fn, input_fn, predict_fn, output_fn\n",
"modelB_path = 'model-korsts'\n",
"\n",
"with open('samples/korsts.txt', mode='rb') as file:\n",
" modelB_input_data = file.read() \n",
" \n",
"modelB = model_fn(modelB_path)\n",
"transformed_inputs = input_fn(modelB_input_data)\n",
"predicted_classes_jsonlines = predict_fn(transformed_inputs, modelB)\n",
"modelB_outputs = output_fn(predicted_classes_jsonlines)\n",
"print(modelB_outputs[0])"
]
},
{
"cell_type": "markdown",
"id": "dc7cf251",
"metadata": {},
"source": [
"### Model C: KoBART (Korean Bidirectional and Auto-Regressive Transformers)\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": "3e15867c",
"metadata": {},
"outputs": [],
"source": [
"!pygmentize src/inference_kobart.py"
]
},
{
"cell_type": "markdown",
"id": "35b82dcf",
"metadata": {},
"source": [
"S3로 모델 아티팩트를 복사하는 대신 Huggingface에 등록된 모델을 그대로 사용합니다. model.pth는 0바이트의 빈 파일이며, 추론을 수행하기 위한 소스 코드들만 아카이빙됩니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34d30133",
"metadata": {},
"outputs": [],
"source": [
"from src.inference_kobart import model_fn, transform_fn\n",
"modelC_path = 'model-kobart'\n",
"f = open(f\"{modelC_path}/model.pth\", 'w')\n",
"f.close()\n",
"\n",
"with open('samples/kobart.txt', mode='rb') as file:\n",
" modelC_input_data = file.read()\n",
"\n",
"modelC = model_fn('./')\n",
"outputs = transform_fn(modelC, modelC_input_data)\n",
"\n",
"with open('samples/kobart.txt', mode='rb') as file:\n",
" modelC_input_data = file.read()"
]
},
{
"cell_type": "markdown",
"id": "3cf70750",
"metadata": {},
"source": [
"결괏값들을 확인했다면 로컬 모드로 빠르게 배포하여 테스트하는 것을 권장드립니다. 단, SageMaker Studio는 로컬 모드를 지원하지 않기 때문에 아래 섹션은 SageMaker에서 실행해 주세요."
]
},
{
"cell_type": "markdown",
"id": "aa4fd9a8",
"metadata": {},
"source": [
"
\n",
"\n",
"## 2. (SageMaker Only) Local Mode Deployment for Model A\n",
"---\n",
"\n",
"### Deploy Model A"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f1fee88",
"metadata": {},
"outputs": [],
"source": [
"modelA_artifact_name = 'modelA.tar.gz'\n",
"prepare_model_artifact(modelA_path, model_artifact_name=modelA_artifact_name)\n",
"local_model_path = f'file://{os.getcwd()}/{modelA_artifact_name}'\n",
"\n",
"model = PyTorchModel(\n",
" model_data=local_model_path,\n",
" role=role,\n",
" entry_point='inference_nsmc.py', \n",
" source_dir='src',\n",
" framework_version='1.7.1',\n",
" py_version='py3',\n",
" predictor_cls=NLPPredictor,\n",
")\n",
"\n",
"predictor = model.deploy(\n",
" initial_instance_count=1,\n",
" instance_type='local'\n",
")"
]
},
{
"cell_type": "markdown",
"id": "d5b3df56",
"metadata": {},
"source": [
"### Invoke using SageMaker Python SDK\n",
"SageMaker SDK `predict()` 메서드로 간단하게 추론을 실행할 수 있습니다. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60faac55",
"metadata": {},
"outputs": [],
"source": [
"inputs = [{\"text\": [\"이 영화는 최고의 영화입니다\"]}, \n",
" {\"text\": [\"최악이에요. 배우의 연기력도 좋지 않고 내용도 너무 허접합니다\"]}]\n",
"\n",
"predicted_classes = predictor.predict(inputs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f116da2f",
"metadata": {},
"outputs": [],
"source": [
"for c in predicted_classes:\n",
" print(c)"
]
},
{
"cell_type": "markdown",
"id": "135ba71c",
"metadata": {},
"source": [
"### Invoke using Boto3 API\n",
"이번에는 boto3의 `invoke_endpoint()` 메서드로 추론을 수행해 보겠습니다.\n",
"Boto3는 서비스 레벨의 low-level SDK로, ML 실험에 초점을 맞춰 일부 기능들이 추상화된 high-level SDK인 SageMaker SDK와 달리 SageMaker API를 완벽하게 제어할 수 있습으며, 프로덕션 및 자동화 작업에 적합합니다. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19567741",
"metadata": {},
"outputs": [],
"source": [
"local_sm_runtime = sagemaker.local.LocalSagemakerRuntimeClient()\n",
"endpoint_name = model.endpoint_name\n",
"\n",
"response = local_sm_runtime.invoke_endpoint(\n",
" EndpointName=endpoint_name, \n",
" ContentType='application/jsonlines',\n",
" Accept='application/jsonlines',\n",
" Body=modelA_input_data\n",
" )\n",
"outputs = response['Body'].read().decode() "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8911818f",
"metadata": {},
"outputs": [],
"source": [
"print_outputs(outputs) "
]
},
{
"cell_type": "markdown",
"id": "f7911e93",
"metadata": {},
"source": [
"### Local Mode Endpoint Clean-up\n",
"엔드포인트를 계속 사용하지 않는다면, 엔드포인트를 삭제해야 합니다. SageMaker SDK에서는 `delete_endpoint()` 메소드로 간단히 삭제할 수 있습니다.\n",
"참고로, 노트북 인스턴스에서 추론 컨테이너를 배포했기 때문에 엔드포인트를 띄워 놓아도 별도로 추가 요금이 과금되지는 않습니다.\n",
"\n",
"로컬 엔드포인트는 도커 컨테이너이기 때문에 `docker rm $(docker ps -a -q)` 으로도 간단히 삭제할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8fd3027",
"metadata": {},
"outputs": [],
"source": [
"predictor.delete_endpoint()"
]
},
{
"cell_type": "markdown",
"id": "8a6cfc47",
"metadata": {},
"source": [
"
\n",
"\n",
"## 3. (SageMaker Only) Local Mode Deployment for Model B\n",
"---\n",
"\n",
"### Deploy Model B"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f5a5877a",
"metadata": {},
"outputs": [],
"source": [
"modelB_artifact_name = 'modelB.tar.gz'\n",
"prepare_model_artifact(modelB_path, model_artifact_name=modelB_artifact_name)\n",
"local_model_path = f'file://{os.getcwd()}/{modelB_artifact_name}'\n",
"\n",
"model = PyTorchModel(\n",
" model_data=local_model_path,\n",
" role=role,\n",
" entry_point='inference_korsts.py', \n",
" source_dir='src',\n",
" framework_version='1.7.1',\n",
" py_version='py3',\n",
" predictor_cls=NLPPredictor,\n",
")\n",
"\n",
"predictor = model.deploy(\n",
" initial_instance_count=1,\n",
" instance_type='local'\n",
")"
]
},
{
"cell_type": "markdown",
"id": "69f70e2d",
"metadata": {},
"source": [
"### Invoke using SageMaker Python SDK\n",
"SageMaker SDK `predict()` 메서드로 간단하게 추론을 실행할 수 있습니다. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4ceb1b0",
"metadata": {},
"outputs": [],
"source": [
"inputs = [{\"text\": [\"맛있는 라면을 먹고 싶어요\", \"후루룩 쩝쩝 후루룩 쩝쩝 맛좋은 라면\"]}, \n",
" {\"text\": [\"뽀로로는 내친구\", \"머신러닝은 러닝머신이 아닙니다.\"]}]\n",
"\n",
"predicted_classes = predictor.predict(inputs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c01c1326",
"metadata": {},
"outputs": [],
"source": [
"for c in predicted_classes:\n",
" print(c)"
]
},
{
"cell_type": "markdown",
"id": "1e9db07a",
"metadata": {},
"source": [
"### Invoke using Boto3 API\n",
"이번에는 boto3의 `invoke_endpoint()` 메서드로 추론을 수행해 보겠습니다.\n",
"Boto3는 서비스 레벨의 low-level SDK로, ML 실험에 초점을 맞춰 일부 기능들이 추상화된 high-level SDK인 SageMaker SDK와 달리 SageMaker API를 완벽하게 제어할 수 있습으며, 프로덕션 및 자동화 작업에 적합합니다. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e5f0bee",
"metadata": {},
"outputs": [],
"source": [
"local_sm_runtime = sagemaker.local.LocalSagemakerRuntimeClient()\n",
"endpoint_name = model.endpoint_name\n",
"\n",
"response = local_sm_runtime.invoke_endpoint(\n",
" EndpointName=endpoint_name, \n",
" ContentType='application/jsonlines',\n",
" Accept='application/jsonlines',\n",
" Body=modelB_input_data\n",
" )\n",
"outputs = response['Body'].read().decode() "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "509865bf",
"metadata": {},
"outputs": [],
"source": [
"print_outputs(outputs)"
]
},
{
"cell_type": "markdown",
"id": "ca850e0b",
"metadata": {},
"source": [
"### Local Mode Endpoint Clean-up\n",
"엔드포인트를 계속 사용하지 않는다면, 엔드포인트를 삭제해야 합니다. SageMaker SDK에서는 `delete_endpoint()` 메소드로 간단히 삭제할 수 있습니다.\n",
"참고로, 노트북 인스턴스에서 추론 컨테이너를 배포했기 때문에 엔드포인트를 띄워 놓아도 별도로 추가 요금이 과금되지는 않습니다.\n",
"\n",
"로컬 엔드포인트는 도커 컨테이너이기 때문에 `docker rm $(docker ps -a -q)` 으로도 간단히 삭제할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "424682d3",
"metadata": {},
"outputs": [],
"source": [
"predictor.delete_endpoint()"
]
},
{
"cell_type": "markdown",
"id": "1abd48a2",
"metadata": {},
"source": [
"
\n",
"\n",
"## 4. (SageMaker Only) Local Mode Deployment for Model C\n",
"---\n",
"\n",
"### Deploy Model C"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41d2a05d",
"metadata": {},
"outputs": [],
"source": [
"modelC_artifact_name = 'modelC.tar.gz'\n",
"prepare_model_artifact(modelC_path, model_artifact_name=modelC_artifact_name)\n",
"local_model_path = f'file://{os.getcwd()}/{modelC_artifact_name}'\n",
"\n",
"model = PyTorchModel(\n",
" model_data=local_model_path,\n",
" role=role,\n",
" entry_point='inference_kobart.py', \n",
" source_dir='src',\n",
" framework_version='1.7.1',\n",
" py_version='py3',\n",
" predictor_cls=NLPPredictor,\n",
")\n",
"\n",
"predictor = model.deploy(\n",
" initial_instance_count=1,\n",
" instance_type='local'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9784a6d",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"time.sleep(3)"
]
},
{
"cell_type": "markdown",
"id": "d007dae4",
"metadata": {},
"source": [
"### Invoke using Boto3 API\n",
"**[주의]** BART 모델은 Auto-Regressive 모델로 내부적으로 연산을 많이 수행하여 기본 인스턴스(예: `ml.t2.medium`)를 사용하는 경우, 시간이 상대적으로 오래 소요됩니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "faf9e331",
"metadata": {},
"outputs": [],
"source": [
"local_sm_runtime = sagemaker.local.LocalSagemakerRuntimeClient()\n",
"endpoint_name = model.endpoint_name\n",
"\n",
"response = local_sm_runtime.invoke_endpoint(\n",
" EndpointName=endpoint_name, \n",
" ContentType='application/jsonlines',\n",
" Accept='application/jsonlines',\n",
" Body=modelC_input_data\n",
" )\n",
"outputs = response['Body'].read().decode() "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "136d6d6b",
"metadata": {},
"outputs": [],
"source": [
"print_outputs(outputs)"
]
},
{
"cell_type": "markdown",
"id": "1bd8d3f1",
"metadata": {},
"source": [
"
\n",
"\n",
"## Local Mode Endpoint Clean up\n",
"---\n",
"\n",
"엔드포인트를 계속 사용하지 않는다면, 엔드포인트를 삭제해야 합니다. SageMaker SDK에서는 `delete_endpoint()` 메소드로 간단히 삭제할 수 있습니다.\n",
"참고로, 노트북 인스턴스에서 추론 컨테이너를 배포했기 때문에 엔드포인트를 띄워 놓아도 별도로 추가 요금이 과금되지는 않습니다.\n",
"\n",
"로컬 엔드포인트는 도커 컨테이너이기 때문에 `docker rm $(docker ps -a -q)` 으로도 간단히 삭제할 수 있습니다."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab757646",
"metadata": {},
"outputs": [],
"source": [
"predictor.delete_endpoint()"
]
}
],
"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
}