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