├── .gitignore ├── PEFT ├── 0_setup.ipynb ├── 1_prepare-dataset-alpaca-method.ipynb ├── 1_prepare-dataset-chunk-method.ipynb ├── 2_local-infer-debug-lora.ipynb ├── 2_local-train-debug-lora.ipynb ├── 3_sm-train-lora.ipynb ├── 4_sm-serving-djl.ipynb ├── readme.md └── src │ ├── local-run-wandb.sh │ ├── local-run.sh │ ├── requirements.txt │ ├── run-wandb.sh │ ├── run.sh │ ├── secrets.env │ └── train.py ├── RAG-SageMaker ├── dataset │ └── fsi_smart_faq_ko.csv ├── images │ ├── TensorShard.png │ ├── architecture-rag-opensearch.png │ ├── open1.png │ ├── open2.png │ ├── open3.png │ ├── open4.png │ ├── open5.png │ ├── open6.png │ ├── open7.png │ ├── open8.png │ └── rag-lang.png ├── indexer │ └── fsi_faq_indexer_ko │ │ ├── index.faiss │ │ └── index.pkl ├── rag-fsi-data-workshop │ ├── README.md │ ├── TASK-0_Setup.ipynb │ ├── TASK-1_Embedding_Vector_Model_Creation.ipynb │ ├── TASK-2-optional_Polyglot_12.8B_Korea_LLM_Model_Creation.ipynb │ ├── TASK-2_Polyglot_5.8B_Korea_LLM_Model_Creation.ipynb │ ├── TASK-3_FSI_FAQ_Faiss_Vector_Search_Local_Store_Test.ipynb │ ├── TASK-4_OpenSearch_Creation_and_Vector_Insertion.ipynb │ ├── TASK-5_OpenSearch_LLM_RAG_Streamlit_Chatbot_Example.py │ ├── requirements.txt │ └── src │ │ └── kullm-polyglot-5-8b-v2 │ │ ├── model.py │ │ └── serving.properties └── utils │ ├── inference_utils.py │ ├── kullm.json │ └── streamlit_util.py ├── README.md ├── common_code ├── inference_lib.py └── kullm.json ├── images ├── Nfloat.png ├── TensorShard.png ├── lora.png ├── lora_eq1.png ├── lora_r.png ├── qlora_eq.png ├── qlora_fig1.png └── quantization.png ├── templates ├── README.md ├── alpaca.json ├── alpaca_legacy.json ├── alpaca_short.json ├── korwkv.json ├── kullm.json └── vigogne.json └── utils ├── __init__.py ├── callbacks.py ├── common_lib.py └── inference_lib.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | target/ 3 | .DS_STORE 4 | .settings/* 5 | javadocs/* 6 | .classpath 7 | .project 8 | logs/ 9 | .idea 10 | __pycache__/ 11 | 12 | # IntelliJ IDEA 13 | **/.idea 14 | *.iml= 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | ### JetBrains template 42 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 43 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 44 | 45 | # User-specific stuff: 46 | .idea/workspace.xml 47 | .idea/tasks.xml 48 | 49 | # Sensitive or high-churn files: 50 | .idea/dataSources/ 51 | .idea/dataSources.ids 52 | .idea/dataSources.xml 53 | .idea/dataSources.local.xml 54 | .idea/sqlDataSources.xml 55 | .idea/dynamic.xml 56 | .idea/uiDesigner.xml 57 | 58 | # PyInstaller 59 | # Usually these files are written by a python script from a template 60 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 61 | *.manifest 62 | *.spec 63 | 64 | # Installer logs 65 | pip-log.txt 66 | pip-delete-this-directory.txt 67 | 68 | # Unit test / coverage reports 69 | htmlcov/ 70 | .tox/ 71 | .nox/ 72 | .coverage 73 | .coverage.* 74 | .cache 75 | nosetests.xml 76 | coverage.xml 77 | *.cover 78 | *.py,cover 79 | .hypothesis/ 80 | .pytest_cache/ 81 | cover/ 82 | 83 | # Translations 84 | *.mo 85 | *.pot 86 | 87 | # Django stuff: 88 | *.log 89 | local_settings.py 90 | db.sqlite3 91 | db.sqlite3-journal 92 | 93 | # Flask stuff: 94 | instance/ 95 | .webassets-cache 96 | 97 | # Scrapy stuff: 98 | .scrapy 99 | 100 | # Sphinx documentation 101 | docs/_build/ 102 | 103 | # PyBuilder 104 | .pybuilder/ 105 | 106 | # Jupyter Notebook 107 | .ipynb_checkpoints 108 | 109 | # IPython 110 | profile_default/ 111 | ipython_config.py 112 | 113 | # pyenv 114 | # For a library or package, you might want to ignore these files since the code is 115 | # intended to run in multiple environments; otherwise, check them in: 116 | # .python-version 117 | 118 | # pipenv 119 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 120 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 121 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 122 | # install all needed dependencies. 123 | #Pipfile.lock 124 | 125 | # poetry 126 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 127 | # This is especially recommended for binary packages to ensure reproducibility, and is more 128 | # commonly ignored for libraries. 129 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 130 | #poetry.lock 131 | 132 | # pdm 133 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 134 | #pdm.lock 135 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 136 | # in version control. 137 | # https://pdm.fming.dev/#use-with-ide 138 | .pdm.toml 139 | 140 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 141 | __pypackages__/ 142 | 143 | # Celery stuff 144 | celerybeat-schedule 145 | celerybeat.pid 146 | 147 | # SageMath parsed files 148 | *.sage.py 149 | 150 | # Environments 151 | .env 152 | .venv 153 | env/ 154 | venv/ 155 | ENV/ 156 | env.bak/ 157 | venv.bak/ 158 | 159 | # Spyder project settings 160 | .spyderproject 161 | .spyproject 162 | 163 | # Rope project settings 164 | .ropeproject 165 | 166 | # mkdocs documentation 167 | /site 168 | 169 | # mypy 170 | .mypy_cache/ 171 | .dmypy.json 172 | dmypy.json 173 | 174 | # Pyre type checker 175 | .pyre/ 176 | 177 | # pytype static type analyzer 178 | .pytype/ 179 | 180 | # Cython debug symbols 181 | cython_debug/ 182 | 183 | .ipynb_checkpoints 184 | */.ipynb_checkpoints/* 185 | *.pyc 186 | 187 | # PyCharm 188 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 189 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 190 | # and can be added to the global gitignore or merged into this file. For a more nuclear 191 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 192 | #.idea/ 193 | -------------------------------------------------------------------------------- /PEFT/0_setup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "7c5f0126-694e-411b-9380-86f4c1b70d24", 7 | "metadata": { 8 | "tags": [] 9 | }, 10 | "outputs": [], 11 | "source": [ 12 | "!pip install -r src/requirements.txt" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "98ae8673-d1d3-40ea-be62-f1c3aeeead3e", 19 | "metadata": { 20 | "tags": [] 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "!pip install -qU boto3 botocore huggingface_hub sagemaker langchain deepspeed wandb" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "source": [ 30 | "### Change Docker image path to EBS\n", 31 | "#### SageMaker 노트북 인스턴스일 경우 docker image 저장소의 공간이 작아 마운트한 EBS로 변경을 진행합니다.\n", 32 | "SageMaker 노트북 인스턴스에서 로컬 모드 디버깅 시 종종 `No space left` 관련 오류가 발생합니다. 따라서, 도커 이미지/컨테이너가 저장될 폴더를 SageMaker EBS (Amazon Elastic Block Store) 볼륨으로 변경하는 것을 권장합니다. 도커 이미지/컨테이너는 기본적으로 EBS가 아닌 루트 볼륨에 저장하기 때문에(루트 볼륨의 크기는 사용자가 임의로 조정할 수 없습니다!) 고용량의 이미지들을 빌드하면 용량이 꽉 차기 때문입니다." 33 | ], 34 | "metadata": { 35 | "collapsed": false 36 | } 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "outputs": [], 42 | "source": [ 43 | "%%bash\n", 44 | "\n", 45 | "#!/usr/bin/env bash\n", 46 | "\n", 47 | "echo '{\n", 48 | " \"runtimes\": {\n", 49 | " \"nvidia\": {\n", 50 | " \"path\": \"nvidia-container-runtime\",\n", 51 | " \"runtimeArgs\": []\n", 52 | " }\n", 53 | " }\n", 54 | "}' > daemon.json\n", 55 | "\n", 56 | "sudo cp daemon.json /etc/docker/daemon.json && rm daemon.json\n", 57 | "\n", 58 | "DAEMON_PATH=\"/etc/docker\"\n", 59 | "MEMORY_SIZE=10G\n", 60 | "\n", 61 | "FLAG=$(cat $DAEMON_PATH/daemon.json | jq 'has(\"data-root\")')\n", 62 | "# echo $FLAG\n", 63 | "\n", 64 | "if [ \"$FLAG\" == true ]; then\n", 65 | " echo \"Already revised\"\n", 66 | "else\n", 67 | " echo \"Add data-root and default-shm-size=$MEMORY_SIZE\"\n", 68 | " sudo cp $DAEMON_PATH/daemon.json $DAEMON_PATH/daemon.json.bak\n", 69 | " sudo cat $DAEMON_PATH/daemon.json.bak | jq '. += {\"data-root\":\"/home/ec2-user/SageMaker/.container/docker\",\"default-shm-size\":\"'$MEMORY_SIZE'\"}' | sudo tee $DAEMON_PATH/daemon.json > /dev/null\n", 70 | " sudo service docker restart\n", 71 | " echo \"Docker Restart\"\n", 72 | "fi\n", 73 | "\n", 74 | "sudo docker info | grep Root" 75 | ], 76 | "metadata": { 77 | "collapsed": false 78 | } 79 | } 80 | ], 81 | "metadata": { 82 | "kernelspec": { 83 | "display_name": "conda_pytorch_p310", 84 | "language": "python", 85 | "name": "conda_pytorch_p310" 86 | }, 87 | "language_info": { 88 | "codemirror_mode": { 89 | "name": "ipython", 90 | "version": 3 91 | }, 92 | "file_extension": ".py", 93 | "mimetype": "text/x-python", 94 | "name": "python", 95 | "nbconvert_exporter": "python", 96 | "pygments_lexer": "ipython3", 97 | "version": "3.10.10" 98 | } 99 | }, 100 | "nbformat": 4, 101 | "nbformat_minor": 5 102 | } 103 | -------------------------------------------------------------------------------- /PEFT/2_local-train-debug-lora.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "55cfa3d1-2187-4176-8c23-19bdf0925364", 6 | "metadata": {}, 7 | "source": [ 8 | "# Korean LLM (Large Language Model) fine-tuning on Local environment (Debugging)\n", 9 | "---\n", 10 | "\n", 11 | "- 허깅페이스 인증 정보 설정: `huggingface-cli login`\n", 12 | " - https://huggingface.co/join\n", 13 | " - https://huggingface.co/settings/tokens\n", 14 | " \n", 15 | "\n", 16 | "## Overview \n", 17 | "\n", 18 | "본격적으로 SageMaker 훈련 인스턴스로 훈련을 수행하기 전에 SageMaker Notebook / SageMaker Studio / SageMaker Studio Lab 에서 샘플 데이터로 디버깅을 수행합니다.\n", 19 | "물론 온프레미스 환경에서 디버깅을 수행할 수 있다면, 기존 환경과 동일하게 디버깅을 수행하면 됩니다.\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "id": "d8e8020b-9964-4198-aed5-0efca878bf5a", 26 | "metadata": { 27 | "tags": [] 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "%load_ext autoreload\n", 32 | "%autoreload 2\n", 33 | "import sys\n", 34 | "import os\n", 35 | "import torch\n", 36 | "import transformers\n", 37 | "from datasets import load_dataset, load_from_disk\n", 38 | "from transformers import GPTNeoXForCausalLM, GPTNeoXTokenizerFast\n", 39 | "\n", 40 | "sys.path.append('../utils')\n", 41 | "sys.path.append('../templates')" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "96b7bd04-d647-45cc-a340-84505df99f67", 48 | "metadata": { 49 | "tags": [] 50 | }, 51 | "outputs": [], 52 | "source": [ 53 | "%store -r bucket_prefix dataset_prefix s3_data_path dataset_prefix_all" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "id": "e2745978-138f-4b70-8693-64859f4704ea", 60 | "metadata": { 61 | "tags": [] 62 | }, 63 | "outputs": [], 64 | "source": [ 65 | "try:\n", 66 | " dataset_prefix\n", 67 | "except NameError:\n", 68 | " print(\"++++++++++++++++++++++++++++++++++++++++++++++++++++++++\")\n", 69 | " print(\"[ERROR] 1번 모듈 노트북을 다시 실행해 주세요.\")\n", 70 | " print(\"++++++++++++++++++++++++++++++++++++++++++++++++++++++++\")" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 4, 76 | "id": "d44b3088-f0b9-4897-a556-933475a9880a", 77 | "metadata": { 78 | "tags": [] 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "lm_dataset = load_from_disk(dataset_prefix)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 5, 88 | "id": "70a0cd87", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "#lm_dataset = load_from_disk(dataset_prefix_all)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 6, 98 | "id": "dfe7d512", 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/plain": [ 104 | "Dataset({\n", 105 | " features: ['input_ids', 'attention_mask', 'labels'],\n", 106 | " num_rows: 50\n", 107 | "})" 108 | ] 109 | }, 110 | "execution_count": 6, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "lm_dataset" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 7, 122 | "id": "5497012c", 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "text/plain": [ 128 | "'chunk-train'" 129 | ] 130 | }, 131 | "execution_count": 7, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "dataset_prefix" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "id": "624f77db-7a07-4439-98c0-2e19cc72e0cb", 143 | "metadata": {}, 144 | "source": [ 145 | "
\n", 146 | "\n", 147 | "## 1. Load Model\n", 148 | "---\n", 149 | "한정된 GPU 메모리 안에 LLM 모델을 로드하는 것은 매우 어렵습니다. 예컨대 20B의 모델을 로드하려면 fp32 기준으로 80GB 이상의 메모리가 필요하고 fp16 기준으로도 40GB 이상의 GPU 메모리가 필요하며, 파인 튜닝을 수행하는 경우는 이보다 더욱 많은 GPU 메모리가 필요합니다. 이런 경우 4비트 양자화와 LoRA를 사용하면 범용적으로 사용하고 있는 16GB 및 24GB GPU 메모리로도 파인 튜닝이 가능합니다. 현 기준으로는 4비트 양자화를 지원하는 QLoRA 기법이 가장 널리 사용되고 있으며 bitsandbytes를 사용하여 QLoRA를 쉽게 적용할 수 있습니다. QLoRA는 양자화된 파라미터의 분포 범위를 정규 분포 내로 억제하여 정밀도의 저하를 방지하는 4비트 NormalFloat 양자화 양자화를 적용하는 정수에 대해서도 양자화를 적용하는 이중 양자화, 그리고 optimizer state 등의 데이터를 CPU 메모리에 저장하는 페이징 기법을 적용하여 GPU 메모리 사용량을 억제합니다. QLoRA에 대한 자세한 내용은 논문 (https://arxiv.org/pdf/2305.14314.pdf) 을 참조하기 바랍니다.\n", 150 | "\n", 151 | "### Create a bitsandbytes configuration" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 8, 157 | "id": "965584cc-3956-4711-8eb4-4fbdaa41f397", 158 | "metadata": { 159 | "tags": [] 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "from transformers import BitsAndBytesConfig\n", 164 | "quant_4bit = True\n", 165 | "quant_8bit = False\n", 166 | "\n", 167 | "if quant_4bit:\n", 168 | " nf4_config = BitsAndBytesConfig(\n", 169 | " load_in_4bit=True,\n", 170 | " bnb_4bit_quant_type=\"nf4\",\n", 171 | " bnb_4bit_use_double_quant=True,\n", 172 | " bnb_4bit_compute_dtype=torch.bfloat16\n", 173 | ")\n", 174 | "else:\n", 175 | " nf4_config = None" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 9, 181 | "id": "f50e68b0", 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "torch.cuda.empty_cache()" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 10, 191 | "id": "9da734ff-5488-4ead-9004-9b5a049b0070", 192 | "metadata": { 193 | "tags": [] 194 | }, 195 | "outputs": [], 196 | "source": [ 197 | "import os\n", 198 | "from pathlib import Path\n", 199 | "from huggingface_hub import snapshot_download\n", 200 | "\n", 201 | "HF_MODEL_ID = \"nlpai-lab/kullm-polyglot-12.8b-v2\"\n", 202 | "\n", 203 | "# create model dir\n", 204 | "model_name = HF_MODEL_ID.split(\"/\")[-1].replace('.', '-')\n", 205 | "model_tar_dir = Path(f\"/home/ec2-user/SageMaker/models/{model_name}\")" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 11, 211 | "id": "7b565d1a-5969-4bd3-8233-cce8ad1e245e", 212 | "metadata": { 213 | "tags": [] 214 | }, 215 | "outputs": [ 216 | { 217 | "name": "stderr", 218 | "output_type": "stream", 219 | "text": [ 220 | "The tokenizer class you load from this checkpoint is not the same type as the class this function is called from. It may result in unexpected tokenization. \n", 221 | "The tokenizer class you load from this checkpoint is 'PreTrainedTokenizerFast'. \n", 222 | "The class this function is called from is 'GPTNeoXTokenizerFast'.\n", 223 | "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n" 224 | ] 225 | }, 226 | { 227 | "data": { 228 | "application/vnd.jupyter.widget-view+json": { 229 | "model_id": "363ea0fd653b4a6f8cf238d773f684c9", 230 | "version_major": 2, 231 | "version_minor": 0 232 | }, 233 | "text/plain": [ 234 | "Loading checkpoint shards: 0%| | 0/3 [00:00\n", 327 | "\n", 328 | "## 2. Training\n", 329 | "---\n", 330 | "### Setting Hyperparameters" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 15, 336 | "id": "415f898f-760f-4ffa-ba5c-3e93c0dee47b", 337 | "metadata": { 338 | "tags": [] 339 | }, 340 | "outputs": [], 341 | "source": [ 342 | "train_data = lm_dataset\n", 343 | "val_data = None\n", 344 | "num_epochs = 3\n", 345 | "batch_size = 2\n", 346 | "\n", 347 | "learning_rate = 3e-5\n", 348 | "gradient_accumulation_steps = 2\n", 349 | "val_set_size = 0\n", 350 | "output_dir = 'output'\n", 351 | "world_size = 1\n", 352 | "ddp = world_size != 1\n", 353 | "group_by_length = False" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 16, 359 | "id": "55083ad0-a671-4634-9912-e036b18f29d9", 360 | "metadata": { 361 | "tags": [] 362 | }, 363 | "outputs": [], 364 | "source": [ 365 | "train_args = transformers.TrainingArguments(\n", 366 | " per_device_train_batch_size=batch_size,\n", 367 | " gradient_accumulation_steps=gradient_accumulation_steps,\n", 368 | " warmup_steps=100,\n", 369 | " num_train_epochs=num_epochs,\n", 370 | " learning_rate=learning_rate,\n", 371 | " bf16=True,\n", 372 | " logging_steps=2,\n", 373 | " optim=\"paged_adamw_8bit\",\n", 374 | " evaluation_strategy=\"steps\" if val_set_size > 0 else \"no\",\n", 375 | " save_strategy=\"steps\",\n", 376 | " eval_steps=200 if val_set_size > 0 else None,\n", 377 | " save_steps=10,\n", 378 | " output_dir=output_dir,\n", 379 | " load_best_model_at_end=True if val_set_size > 0 else False,\n", 380 | " ddp_find_unused_parameters=False if ddp else None,\n", 381 | " report_to=\"none\",\n", 382 | " group_by_length=group_by_length,\n", 383 | ")\n", 384 | "\n", 385 | "trainer = transformers.Trainer(\n", 386 | " model=model,\n", 387 | " train_dataset=train_data,\n", 388 | " eval_dataset=val_data,\n", 389 | " args=train_args,\n", 390 | " data_collator=transformers.DataCollatorForSeq2Seq(\n", 391 | " tokenizer, pad_to_multiple_of=8, return_tensors=\"pt\", padding=True\n", 392 | " ),\n", 393 | ")" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 17, 399 | "id": "6208dfc0", 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "data": { 404 | "text/plain": [ 405 | "PeftModelForCausalLM(\n", 406 | " (base_model): LoraModel(\n", 407 | " (model): GPTNeoXForCausalLM(\n", 408 | " (gpt_neox): GPTNeoXModel(\n", 409 | " (embed_in): Embedding(30080, 5120)\n", 410 | " (emb_dropout): Dropout(p=0.0, inplace=False)\n", 411 | " (layers): ModuleList(\n", 412 | " (0-39): 40 x GPTNeoXLayer(\n", 413 | " (input_layernorm): LayerNorm((5120,), eps=1e-05, elementwise_affine=True)\n", 414 | " (post_attention_layernorm): LayerNorm((5120,), eps=1e-05, elementwise_affine=True)\n", 415 | " (post_attention_dropout): Dropout(p=0.0, inplace=False)\n", 416 | " (post_mlp_dropout): Dropout(p=0.0, inplace=False)\n", 417 | " (attention): GPTNeoXAttention(\n", 418 | " (rotary_emb): GPTNeoXRotaryEmbedding()\n", 419 | " (query_key_value): lora.Linear4bit(\n", 420 | " (base_layer): Linear4bit(in_features=5120, out_features=15360, bias=True)\n", 421 | " (lora_dropout): ModuleDict(\n", 422 | " (default): Dropout(p=0.05, inplace=False)\n", 423 | " )\n", 424 | " (lora_A): ModuleDict(\n", 425 | " (default): Linear(in_features=5120, out_features=8, bias=False)\n", 426 | " )\n", 427 | " (lora_B): ModuleDict(\n", 428 | " (default): Linear(in_features=8, out_features=15360, bias=False)\n", 429 | " )\n", 430 | " (lora_embedding_A): ParameterDict()\n", 431 | " (lora_embedding_B): ParameterDict()\n", 432 | " )\n", 433 | " (dense): Linear4bit(in_features=5120, out_features=5120, bias=True)\n", 434 | " (attention_dropout): Dropout(p=0.0, inplace=False)\n", 435 | " )\n", 436 | " (mlp): GPTNeoXMLP(\n", 437 | " (dense_h_to_4h): Linear4bit(in_features=5120, out_features=20480, bias=True)\n", 438 | " (dense_4h_to_h): Linear4bit(in_features=20480, out_features=5120, bias=True)\n", 439 | " (act): GELUActivation()\n", 440 | " )\n", 441 | " )\n", 442 | " )\n", 443 | " (final_layer_norm): LayerNorm((5120,), eps=1e-05, elementwise_affine=True)\n", 444 | " )\n", 445 | " (embed_out): Linear(in_features=5120, out_features=30080, bias=False)\n", 446 | " )\n", 447 | " )\n", 448 | ")" 449 | ] 450 | }, 451 | "execution_count": 17, 452 | "metadata": {}, 453 | "output_type": "execute_result" 454 | } 455 | ], 456 | "source": [ 457 | "model" 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "id": "66470982-1dad-4e3d-9e29-a736fab51cf0", 463 | "metadata": {}, 464 | "source": [ 465 | "### Start Training" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": 18, 471 | "id": "2a70473b-78dd-4ede-9b81-4980e9404c3f", 472 | "metadata": { 473 | "tags": [] 474 | }, 475 | "outputs": [ 476 | { 477 | "name": "stderr", 478 | "output_type": "stream", 479 | "text": [ 480 | "You're using a GPTNeoXTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.\n", 481 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", 482 | " warnings.warn(\n" 483 | ] 484 | }, 485 | { 486 | "data": { 487 | "text/html": [ 488 | "\n", 489 | "
\n", 490 | " \n", 491 | " \n", 492 | " [36/36 09:47, Epoch 2/3]\n", 493 | "
\n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | "
StepTraining Loss
21.970600
41.837500
61.886500
81.978900
101.923500
121.842900
141.934400
161.957300
181.807000
201.895300
221.961800
241.897600
261.890900
281.920700
301.868100
321.872200
341.945600
361.869000

" 576 | ], 577 | "text/plain": [ 578 | "" 579 | ] 580 | }, 581 | "metadata": {}, 582 | "output_type": "display_data" 583 | }, 584 | { 585 | "name": "stderr", 586 | "output_type": "stream", 587 | "text": [ 588 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", 589 | " warnings.warn(\n", 590 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", 591 | " warnings.warn(\n", 592 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", 593 | " warnings.warn(\n" 594 | ] 595 | } 596 | ], 597 | "source": [ 598 | "model.config.use_cache = False\n", 599 | "\n", 600 | "# old_state_dict = model.state_dict\n", 601 | "# model.state_dict = (lambda self, *_, **__: get_peft_model_state_dict(self, old_state_dict())).__get__(\n", 602 | "# model, type(model)\n", 603 | "# )\n", 604 | "\n", 605 | "if torch.__version__ >= \"2\" and sys.platform != \"win32\":\n", 606 | " model = torch.compile(model)\n", 607 | "\n", 608 | "train_result = trainer.train()" 609 | ] 610 | }, 611 | { 612 | "cell_type": "markdown", 613 | "id": "841d0a1a-d4f6-4650-a2f2-af5375a126a5", 614 | "metadata": {}, 615 | "source": [ 616 | "### Check metrics" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": 19, 622 | "id": "31e72538-ca9e-4fe0-9927-61c4e24d6f7a", 623 | "metadata": { 624 | "tags": [] 625 | }, 626 | "outputs": [ 627 | { 628 | "name": "stdout", 629 | "output_type": "stream", 630 | "text": [ 631 | "***** train metrics *****\n", 632 | " epoch = 2.88\n", 633 | " total_flos = 15753753GF\n", 634 | " train_loss = 1.9033\n", 635 | " train_runtime = 0:10:06.24\n", 636 | " train_samples_per_second = 0.247\n", 637 | " train_steps_per_second = 0.059\n" 638 | ] 639 | } 640 | ], 641 | "source": [ 642 | "metrics = train_result.metrics\n", 643 | "trainer.log_metrics(\"train\", metrics)\n", 644 | "#trainer.save_metrics(\"train\", metrics)" 645 | ] 646 | }, 647 | { 648 | "cell_type": "markdown", 649 | "id": "3c8155ff-b6b8-493a-96bf-321f605f9fad", 650 | "metadata": {}, 651 | "source": [ 652 | "### Save fine-tuned model" 653 | ] 654 | }, 655 | { 656 | "cell_type": "code", 657 | "execution_count": 20, 658 | "id": "52e04694-47c4-45b1-a034-09812e21dc5d", 659 | "metadata": { 660 | "tags": [] 661 | }, 662 | "outputs": [], 663 | "source": [ 664 | "trainer.model.save_pretrained(output_dir)" 665 | ] 666 | }, 667 | { 668 | "cell_type": "code", 669 | "execution_count": 21, 670 | "id": "92919402", 671 | "metadata": {}, 672 | "outputs": [ 673 | { 674 | "data": { 675 | "text/plain": [ 676 | "('output/tokenizer_config.json',\n", 677 | " 'output/special_tokens_map.json',\n", 678 | " 'output/tokenizer.json')" 679 | ] 680 | }, 681 | "execution_count": 21, 682 | "metadata": {}, 683 | "output_type": "execute_result" 684 | } 685 | ], 686 | "source": [ 687 | "tokenizer.save_pretrained(output_dir)" 688 | ] 689 | }, 690 | { 691 | "cell_type": "code", 692 | "execution_count": 22, 693 | "id": "8dc4970b", 694 | "metadata": {}, 695 | "outputs": [ 696 | { 697 | "name": "stdout", 698 | "output_type": "stream", 699 | "text": [ 700 | "Stored 'output_dir' (str)\n" 701 | ] 702 | } 703 | ], 704 | "source": [ 705 | "%store output_dir" 706 | ] 707 | }, 708 | { 709 | "cell_type": "code", 710 | "execution_count": 23, 711 | "id": "1a93c053-0543-4da7-9e6a-7b0f95e160d3", 712 | "metadata": { 713 | "tags": [] 714 | }, 715 | "outputs": [], 716 | "source": [ 717 | "# Free memory for merging weights\n", 718 | "#del model\n", 719 | "#del trainer\n", 720 | "#torch.cuda.empty_cache()" 721 | ] 722 | }, 723 | { 724 | "cell_type": "code", 725 | "execution_count": 24, 726 | "id": "e56fad97", 727 | "metadata": {}, 728 | "outputs": [], 729 | "source": [ 730 | "model.eval()\n", 731 | "model.config.use_cache = True # silence the warnings. Please re-enable for inference!" 732 | ] 733 | }, 734 | { 735 | "cell_type": "code", 736 | "execution_count": 25, 737 | "id": "d8157c05", 738 | "metadata": {}, 739 | "outputs": [ 740 | { 741 | "name": "stderr", 742 | "output_type": "stream", 743 | "text": [ 744 | "Setting `pad_token_id` to `eos_token_id`:0 for open-end generation.\n", 745 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/transformers/generation/utils.py:1355: UserWarning: Using the model-agnostic default `max_length` (=20) to control the generation length. We recommend setting `max_new_tokens` to control the maximum length of the generation.\n", 746 | " warnings.warn(\n", 747 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/transformers/generation/utils.py:1636: UserWarning: You are calling .generate() with the `input_ids` being on a device type different than your model's device. `input_ids` is on cpu, whereas the model is on cuda. You may experience unexpected behaviors or slower generation. Please make sure that you have put `input_ids` to the correct device by calling for example input_ids = input_ids.to('cuda') before running `.generate()`.\n", 748 | " warnings.warn(\n" 749 | ] 750 | }, 751 | { 752 | "data": { 753 | "text/plain": [ 754 | "tensor([[ 6, 6, 6, 2438, 29, 5565, 462, 272, 26343, 270,\n", 755 | " 6976, 489, 17, 503, 293, 7735, 272, 388, 296, 3133]])" 756 | ] 757 | }, 758 | "execution_count": 25, 759 | "metadata": {}, 760 | "output_type": "execute_result" 761 | } 762 | ], 763 | "source": [ 764 | "model.generate(**tokenizer(\"### 질문: 안녕 나는 빡빡이 아저씨야\", return_tensors='pt', return_token_type_ids=False))" 765 | ] 766 | }, 767 | { 768 | "cell_type": "code", 769 | "execution_count": 26, 770 | "id": "d8f90e73", 771 | "metadata": {}, 772 | "outputs": [], 773 | "source": [ 774 | "def gen(x):\n", 775 | " gened = model.generate(\n", 776 | " **tokenizer(\n", 777 | " f\"### 질문: {x}\\n\\n### 답변:\", \n", 778 | " return_tensors='pt', \n", 779 | " return_token_type_ids=False\n", 780 | " ), \n", 781 | " max_new_tokens=256,\n", 782 | " early_stopping=True,\n", 783 | " do_sample=True,\n", 784 | " eos_token_id=2,\n", 785 | " )\n", 786 | " print(tokenizer.decode(gened[0]))" 787 | ] 788 | }, 789 | { 790 | "cell_type": "code", 791 | "execution_count": 27, 792 | "id": "ced53ddf", 793 | "metadata": {}, 794 | "outputs": [ 795 | { 796 | "name": "stderr", 797 | "output_type": "stream", 798 | "text": [ 799 | "/home/ec2-user/anaconda3/envs/pytorch_p310/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:430: UserWarning: `num_beams` is set to 1. However, `early_stopping` is set to `True` -- this flag is only used in beam-based generation modes. You should set `num_beams>1` or unset `early_stopping`.\n", 800 | " warnings.warn(\n", 801 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 802 | ] 803 | }, 804 | { 805 | "name": "stdout", 806 | "output_type": "stream", 807 | "text": [ 808 | "### 질문: 프로그래밍을 잘 하기 위한 세 가지 방법은?\n", 809 | "\n", 810 | "### 답변:\n", 811 | "세 가지 방법의 예에는 다음이 포함됩니다:1. 알고리즘을 구체적으로 작성하기: 문제를 식별하고, 필요한 것을 정확히 계산하고, 실행 가능한 구현을 작성하여 알고리즘을 구체적이고 이해하기 쉽게 만듭니다.2. 컴퓨터와 더 잘 작업하기: 컴퓨터가 내가 생각하는 것을 이해할 수 있도록 컴퓨터의 언어로 나의 생각을 설명합니다. 이는 코드, 텍스트 설명 또는 기타 구두 표현을 통해 이루어질 수 있습니다.3. 반복하기: 알고리즘을 처음부터 다시 작성합니다. 문제에 대해 새로운 아이디어를 더하고, 알고리즘을 수정하고, 구현을 테스트하여 작업에 대한 이해를 보장합니다.<|endoftext|>\n" 812 | ] 813 | } 814 | ], 815 | "source": [ 816 | "gen('프로그래밍을 잘 하기 위한 세 가지 방법은?')" 817 | ] 818 | }, 819 | { 820 | "cell_type": "markdown", 821 | "id": "47689a08", 822 | "metadata": {}, 823 | "source": [ 824 | "### 2_local-infer-debug-lora 과정을 위해 allocated CUDA memory를 Release합니다. " 825 | ] 826 | }, 827 | { 828 | "cell_type": "code", 829 | "execution_count": 28, 830 | "id": "2f333a7a", 831 | "metadata": {}, 832 | "outputs": [], 833 | "source": [ 834 | "# Free memory for merging weights\n", 835 | "del model\n", 836 | "del trainer\n", 837 | "torch.cuda.empty_cache()" 838 | ] 839 | } 840 | ], 841 | "metadata": { 842 | "kernelspec": { 843 | "display_name": "conda_pytorch_p310", 844 | "language": "python", 845 | "name": "conda_pytorch_p310" 846 | }, 847 | "language_info": { 848 | "codemirror_mode": { 849 | "name": "ipython", 850 | "version": 3 851 | }, 852 | "file_extension": ".py", 853 | "mimetype": "text/x-python", 854 | "name": "python", 855 | "nbconvert_exporter": "python", 856 | "pygments_lexer": "ipython3", 857 | "version": "3.10.13" 858 | } 859 | }, 860 | "nbformat": 4, 861 | "nbformat_minor": 5 862 | } 863 | -------------------------------------------------------------------------------- /PEFT/4_sm-serving-djl.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "5e0df272-7e0d-4235-8915-debd8bd732a4", 6 | "metadata": {}, 7 | "source": [ 8 | "# Korean LLM (Large Language Model) Serving on SageMaker with AWS Large Model Container DLC\n", 9 | "---\n", 10 | "\n", 11 | "- LLM GitHub: https://github.com/nlpai-lab/KULLM\n", 12 | "- Hugging Face model hub: https://huggingface.co/nlpai-lab/kullm-polyglot-5.8b-v2\n", 13 | "- [AWS Blog: Deploy large models on Amazon SageMaker using DJLServing and DeepSpeed model parallel inference](https://aws.amazon.com/ko/blogs/machine-learning/deploy-large-models-on-amazon-sagemaker-using-djlserving-and-deepspeed-model-parallel-inference)\n", 14 | "\n", 15 | "## Overview \n", 16 | "\n", 17 | "기존에 SageMaker 호스팅 인스턴스에서 모델 서빙 시에는 모델 아티팩트를 model.tar.gz 형식으로 아카이브하여 압축하였습니다. 이 방법은 소규모 모델에는 효과적이지만, 수백억~수천억 개의 파라미터를 가진 파운데이션 모델에는 적합하지 않습니다. (참조: https://docs.aws.amazon.com/sagemaker/latest/dg/large-model-inference-uncompressed.html)\n", 18 | " \n", 19 | "본 핸즈온에서는 PEFT로 훈련된 추가 모델 파라미터를 베이스 모델 파라미터에 병합하여 SageMaker의 LMI(Large Model Inference) 컨테이너 환경에 모델을 배포하는 법을 알아봅니다." 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "id": "ffd3227d", 26 | "metadata": { 27 | "tags": [] 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "%load_ext autoreload\n", 32 | "%autoreload 2\n", 33 | "import sys\n", 34 | "sys.path.append('../utils')\n", 35 | "sys.path.append('../templates')\n", 36 | "\n", 37 | "from common_lib import check_packages\n", 38 | "check_packages()\n", 39 | "\n", 40 | "%store -r train_job_name\n", 41 | "\n", 42 | "try:\n", 43 | " train_job_name\n", 44 | "except NameError:\n", 45 | " print(\"++++++++++++++++++++++++++++++++++++++++++++++++++++++++\")\n", 46 | " print(\"[ERROR] 3번 모듈 노트북(3_sm-train-lora.ipynb)을 먼저 실행해 주세요.\")\n", 47 | " print(\"++++++++++++++++++++++++++++++++++++++++++++++++++++++++\")" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "73a8a853", 54 | "metadata": { 55 | "tags": [] 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "import os\n", 60 | "import json\n", 61 | "import boto3\n", 62 | "import torch\n", 63 | "import sagemaker, boto3, jinja2\n", 64 | "from sagemaker import get_execution_role\n", 65 | "from sagemaker.model import Model\n", 66 | "from sagemaker.estimator import Estimator\n", 67 | "from sagemaker import serializers, deserializers\n", 68 | "from peft import AutoPeftModelForCausalLM\n", 69 | "from transformers import AutoTokenizer\n", 70 | "\n", 71 | "role = sagemaker.get_execution_role() # execution role for the endpoint\n", 72 | "sess = sagemaker.session.Session() # sagemaker session for interacting with different AWS APIs\n", 73 | "bucket = sess.default_bucket() # bucket to house artifacts\n", 74 | "region = sess._region_name \n", 75 | "account_id = sess.account_id() # account_id of the current SageMaker Studio environment\n", 76 | "s3_client = boto3.client(\"s3\") # client to intreract with S3 API\n", 77 | "sm_client = boto3.client(\"sagemaker\") # client to intreract with SageMaker\n", 78 | "smr_client = boto3.client(\"sagemaker-runtime\") # client to intreract with SageMaker Endpoints\n", 79 | "\n", 80 | "estimator = Estimator.attach(train_job_name)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "id": "b4b639dc-b1cc-475f-9f30-fb876379b95f", 86 | "metadata": {}, 87 | "source": [ 88 | "
\n", 89 | "\n", 90 | "## 1. Merge PEFT model\n", 91 | "---" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "82d3ff2f-c8bf-4bd8-86e3-ea801c2ea983", 98 | "metadata": { 99 | "tags": [] 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "local_peft_model_dir = 'model_from_sagemaker'\n", 104 | "\n", 105 | "if not os.path.exists(local_peft_model_dir):\n", 106 | " os.makedirs(local_peft_model_dir)\n", 107 | "\n", 108 | "!aws s3 cp {estimator.model_data} {local_peft_model_dir}/model.tar.gz\n", 109 | "!tar -xzf {local_peft_model_dir}/model.tar.gz -C {local_peft_model_dir}\n", 110 | "!rm {local_peft_model_dir}/model.tar.gz" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "id": "8ddab477-5c3d-49d4-b025-3ab50a335b7c", 117 | "metadata": { 118 | "tags": [] 119 | }, 120 | "outputs": [], 121 | "source": [ 122 | "def modify_base_model_path(peft_model_dir, base_model_path):\n", 123 | " with open(f\"{peft_model_dir}/adapter_config.json\", \"r\") as jsonfile:\n", 124 | " data = json.load(jsonfile)\n", 125 | " data[\"base_model_name_or_path\"] = base_model_path\n", 126 | " with open(f\"{peft_model_dir}/adapter_config.json\", \"w\") as jsonfile:\n", 127 | " json.dump(data, jsonfile)\n", 128 | "\n", 129 | "def byte_transform(byte_size, to):\n", 130 | " a = {'k': 1, 'm': 2, 'g': 3, 't': 4, 'p': 5, 'e': 6}\n", 131 | " r = float(byte_size)\n", 132 | " for i in range(a[to]):\n", 133 | " r = r / 1024\n", 134 | " return round(r, 4)\n", 135 | "\n", 136 | "size = 0\n", 137 | "for ele in os.scandir(local_peft_model_dir):\n", 138 | " size += os.path.getsize(ele)\n", 139 | "gb_size = byte_transform(size, 'g')" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "id": "e12ae953-6057-4587-8ead-987097cc6131", 146 | "metadata": { 147 | "tags": [] 148 | }, 149 | "outputs": [], 150 | "source": [ 151 | "if gb_size < 0.5:\n", 152 | " base_model_path = \"/home/ec2-user/SageMaker/models/kullm-polyglot-12-8b-v2\"\n", 153 | " modify_base_model_path(local_peft_model_dir, base_model_path)\n", 154 | " \n", 155 | " local_model_dir = \"model_from_sagemaker_merged\" \n", 156 | " print(f'Save merged model: {local_model_dir}') \n", 157 | " os.makedirs(local_model_dir, exist_ok=True)\n", 158 | " model = AutoPeftModelForCausalLM.from_pretrained(local_peft_model_dir, low_cpu_mem_usage=True, torch_dtype=torch.bfloat16)\n", 159 | " merged_model = model.merge_and_unload()\n", 160 | " merged_model.save_pretrained(local_model_dir, safe_serialization=True)\n", 161 | " \n", 162 | " tokenizer = AutoTokenizer.from_pretrained(local_peft_model_dir)\n", 163 | " tokenizer.save_pretrained(local_model_dir)\n", 164 | "else:\n", 165 | " local_model_dir = local_peft_model_dir" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "id": "4406229a", 171 | "metadata": {}, 172 | "source": [ 173 | "
\n", 174 | "\n", 175 | "## 2. Upload LLM model to S3\n", 176 | "---" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "id": "26c4d12b-05b1-43d6-91c6-9de80720c534", 183 | "metadata": { 184 | "tags": [] 185 | }, 186 | "outputs": [], 187 | "source": [ 188 | "model_prefix = \"kkulm-qlora-12-8B\"\n", 189 | "model_tar_dir = f\"{os.getcwd()}/model_from_sagemaker_merged\"\n", 190 | "\n", 191 | "bucket_prefix = 'ko-llms/serving' \n", 192 | "s3_code_prefix = f\"{bucket_prefix}/{model_prefix}/code\" # folder within bucket where code artifact will go\n", 193 | "s3_model_prefix = f\"{bucket_prefix}/{model_prefix}/model\" # folder where model checkpoint will go\n", 194 | "s3_model_artifact = f\"s3://{bucket}/{s3_model_prefix}\"\n", 195 | "\n", 196 | "print(f\"S3 code prefix \\n {s3_code_prefix}\")\n", 197 | "print(f\"S3 model prefix: \\n {s3_model_prefix}\")\n", 198 | "print(f\"S3 model artifact path: \\n {s3_model_artifact}\")" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "id": "3974a980-1452-4823-98ac-68516bae28cb", 205 | "metadata": { 206 | "tags": [] 207 | }, 208 | "outputs": [], 209 | "source": [ 210 | "%%bash\n", 211 | "aws configure set default.s3.max_concurrent_requests 100\n", 212 | "aws configure set default.s3.max_queue_size 10000\n", 213 | "aws configure set default.s3.multipart_threshold 1GB\n", 214 | "aws configure set default.s3.multipart_chunksize 64MB" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "id": "040f782f-4815-4ee9-8605-d259d61e0e97", 221 | "metadata": { 222 | "tags": [] 223 | }, 224 | "outputs": [], 225 | "source": [ 226 | "!aws s3 sync {model_tar_dir} {s3_model_artifact}\n", 227 | "print(f\"Model uploaded to --- > {s3_model_artifact}\")\n", 228 | "print(f\"We will set option.s3url={s3_model_artifact}\")" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "id": "09506187-4c72-49ee-a450-53855eb2f8d3", 235 | "metadata": { 236 | "tags": [] 237 | }, 238 | "outputs": [], 239 | "source": [ 240 | "src_path = f\"serving_src/{model_prefix}\"\n", 241 | "!rm -rf {src_path}\n", 242 | "os.makedirs(src_path, exist_ok=True)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "id": "51a8cbe6-b1eb-4618-8217-d0ecd8c6428e", 249 | "metadata": { 250 | "tags": [] 251 | }, 252 | "outputs": [], 253 | "source": [ 254 | "%%writefile {src_path}/serving.properties\n", 255 | "option.s3url={{s3url}}\n", 256 | "\n", 257 | "engine=DeepSpeed\n", 258 | "\n", 259 | "# passing extra options to model.py or built-in handler\n", 260 | "job_queue_size=100\n", 261 | "batch_size=1\n", 262 | "max_batch_delay=1\n", 263 | "max_idle_time=60\n", 264 | "\n", 265 | "# Built-in entrypoint\n", 266 | "#option.entryPoint=djl_python.deepspeed\n", 267 | "\n", 268 | "# Hugging Face model id\n", 269 | "#option.model_id=EleutherAI/gpt-j-6B\n", 270 | "\n", 271 | "# defines custom environment variables\n", 272 | "#env=SERVING_NUMBER_OF_NETTY_THREADS=2\n", 273 | "\n", 274 | "# Allows to load DeepSpeed workers in parallel\n", 275 | "option.parallel_loading=true\n", 276 | "\n", 277 | "# specify tensor parallel degree (number of partitions)\n", 278 | "option.tensor_parallel_degree=4\n", 279 | "\n", 280 | "# specify per model timeout\n", 281 | "option.model_loading_timeout=600\n", 282 | "#option.predict_timeout=240\n", 283 | "\n", 284 | "# mark the model as failure after python process crashing 10 times\n", 285 | "retry_threshold=0\n", 286 | "\n", 287 | "option.task=text-generation" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "id": "89f67d0e-6bd4-4919-a400-16f38f7ebcbb", 294 | "metadata": { 295 | "tags": [] 296 | }, 297 | "outputs": [], 298 | "source": [ 299 | "from pathlib import Path\n", 300 | "jinja_env = jinja2.Environment() \n", 301 | "# we plug in the appropriate model location into our `serving.properties` file based on the region in which this notebook is running\n", 302 | "template = jinja_env.from_string(Path(f\"{src_path}/serving.properties\").open().read())\n", 303 | "Path(f\"{src_path}/serving.properties\").open(\"w\").write(template.render(s3url=s3_model_artifact))\n", 304 | "!pygmentize {src_path}/serving.properties | cat -n" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "id": "9ff83d21-555f-47a4-8b73-24fe97db441e", 311 | "metadata": { 312 | "tags": [] 313 | }, 314 | "outputs": [], 315 | "source": [ 316 | "%%writefile {src_path}/model.py\n", 317 | "from djl_python import Input, Output\n", 318 | "import os\n", 319 | "import deepspeed\n", 320 | "import torch\n", 321 | "import logging\n", 322 | "from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer\n", 323 | "from transformers import GPTNeoXLayer\n", 324 | "\n", 325 | "predictor = None\n", 326 | "\n", 327 | "def get_model(properties):\n", 328 | " \n", 329 | " tp_degree = properties[\"tensor_parallel_degree\"]\n", 330 | " model_location = properties[\"model_dir\"]\n", 331 | " if \"model_id\" in properties:\n", 332 | " model_location = properties[\"model_id\"]\n", 333 | " task = properties[\"task\"]\n", 334 | " \n", 335 | " logging.info(f\"Loading model in {model_location}\") \n", 336 | " local_rank = int(os.getenv(\"LOCAL_RANK\", \"0\"))\n", 337 | "\n", 338 | " tokenizer = AutoTokenizer.from_pretrained(model_location)\n", 339 | "\n", 340 | " model = AutoModelForCausalLM.from_pretrained(\n", 341 | " model_location,\n", 342 | " torch_dtype=torch.float16,\n", 343 | " low_cpu_mem_usage=True,\n", 344 | " )\n", 345 | " \n", 346 | " model.requires_grad_(False)\n", 347 | " model.eval()\n", 348 | " \n", 349 | " ds_config = {\n", 350 | " \"tensor_parallel\": {\"tp_size\": tp_degree},\n", 351 | " \"dtype\": model.dtype,\n", 352 | " \"injection_policy\": {\n", 353 | " GPTNeoXLayer:('attention.dense', 'mlp.dense_4h_to_h')\n", 354 | " }\n", 355 | " }\n", 356 | " logging.info(f\"Starting DeepSpeed init with TP={tp_degree}\") \n", 357 | " model = deepspeed.init_inference(model, ds_config) \n", 358 | " \n", 359 | " generator = pipeline(\n", 360 | " task=task, model=model, tokenizer=tokenizer, device=local_rank\n", 361 | " )\n", 362 | " # https://huggingface.co/docs/hub/models-tasks\n", 363 | " return generator\n", 364 | " \n", 365 | "def handle(inputs: Input) -> None:\n", 366 | " \"\"\"\n", 367 | " inputs: Contains the configurations from serving.properties\n", 368 | " \"\"\" \n", 369 | " global predictor\n", 370 | " if not predictor:\n", 371 | " predictor = get_model(inputs.get_properties())\n", 372 | "\n", 373 | " if inputs.is_empty():\n", 374 | " # Model server makes an empty call to warmup the model on startup\n", 375 | " logging.info(\"is_empty\")\n", 376 | " return None\n", 377 | "\n", 378 | " data = inputs.get_as_json() #inputs.get_as_string()\n", 379 | " logging.info(\"data:\", data)\n", 380 | " \n", 381 | " input_prompt, params = data[\"inputs\"], data[\"parameters\"]\n", 382 | " result = predictor(input_prompt, **params)\n", 383 | " logging.info(\"result:\", result)\n", 384 | "\n", 385 | " return Output().add_as_json(result) #Output().add(result) " 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": null, 391 | "id": "cfeca701-a44f-496b-a841-bdbb354bd11c", 392 | "metadata": { 393 | "tags": [] 394 | }, 395 | "outputs": [], 396 | "source": [ 397 | "!rm -rf model.tar.gz\n", 398 | "!tar czvf model.tar.gz -C {src_path} ." 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": null, 404 | "id": "b8f234e3-5588-4bbd-a95f-9c74aa50944e", 405 | "metadata": { 406 | "tags": [] 407 | }, 408 | "outputs": [], 409 | "source": [ 410 | "s3_code_artifact = sess.upload_data(\"model.tar.gz\", bucket, s3_code_prefix)\n", 411 | "print(f\"S3 Code or Model tar ball uploaded to --- > {s3_code_artifact}\")\n", 412 | "!rm -rf model.tar.gz" 413 | ] 414 | }, 415 | { 416 | "cell_type": "markdown", 417 | "id": "c4834546-f482-41ae-b94e-901f8be01e5e", 418 | "metadata": {}, 419 | "source": [ 420 | "
\n", 421 | "\n", 422 | "## 3. Serve LLM Model on SageMaker\n", 423 | "---\n", 424 | "### Create SageMaker Model\n", 425 | "\n", 426 | "SageMaker 엔드포인트 생성 매개변수 VolumeSizeInGB를 지정할 때 마운트되는 Amazon EBS(Amazon Elastic Block Store) 볼륨에 /tmp를 매핑하기 때문에 컨테이너는 인스턴스의 `/tmp` 공간에 모델을 다운로드합니다. 이때 s5cmd (https://github.com/peak/s5cmd) 를 활용하므로 대용량 모델을 빠르게 다운로드할 수 있습니다.\n", 427 | "볼륨 인스턴스와 함께 미리 빌드되어 제공되는 p4dn과 같은 인스턴스의 경우 컨테이너의 `/tmp`를 계속 활용할 수 있습니다. " 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "id": "cf1e0f9c-3eb4-441e-8f22-1e9292abf3bb", 434 | "metadata": { 435 | "tags": [] 436 | }, 437 | "outputs": [], 438 | "source": [ 439 | "from sagemaker.utils import name_from_base\n", 440 | "from sagemaker import image_uris\n", 441 | "\n", 442 | "img_uri = image_uris.retrieve(framework=\"djl-deepspeed\", region=region, version=\"0.23.0\")\n", 443 | "model_name = name_from_base(f\"{model_prefix}\")\n", 444 | "print(model_name)\n", 445 | "\n", 446 | "model_response = sm_client.create_model(\n", 447 | " ModelName=model_name,\n", 448 | " ExecutionRoleArn=role,\n", 449 | " PrimaryContainer={\"Image\": img_uri, \"ModelDataUrl\": s3_code_artifact},\n", 450 | ")\n", 451 | "model_arn = model_response[\"ModelArn\"]\n", 452 | "print(f\"Created Model: {model_arn}\")" 453 | ] 454 | }, 455 | { 456 | "cell_type": "markdown", 457 | "id": "f7aee43a-87cf-4a0f-ad70-5715c745d7ad", 458 | "metadata": {}, 459 | "source": [ 460 | "### Create SageMaker Endpoint" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": null, 466 | "id": "c82f3d85-070a-4cd5-bbfa-6c2ec4afd7d5", 467 | "metadata": { 468 | "tags": [] 469 | }, 470 | "outputs": [], 471 | "source": [ 472 | "endpoint_config_name = f\"{model_name}-config\"\n", 473 | "endpoint_name = f\"{model_name}-endpoint\"\n", 474 | "variant_name = \"variant1\"\n", 475 | "instance_type = \"ml.g5.12xlarge\"\n", 476 | "initial_instance_count = 1\n", 477 | "\n", 478 | "prod_variants = [\n", 479 | " {\n", 480 | " \"VariantName\": \"variant1\",\n", 481 | " \"ModelName\": model_name,\n", 482 | " \"InstanceType\": instance_type,\n", 483 | " \"InitialInstanceCount\": initial_instance_count,\n", 484 | " # \"ModelDataDownloadTimeoutInSeconds\": 2400,\n", 485 | " \"ContainerStartupHealthCheckTimeoutInSeconds\": 600,\n", 486 | " }\n", 487 | "]\n", 488 | "\n", 489 | "endpoint_config_response = sm_client.create_endpoint_config(\n", 490 | " EndpointConfigName=endpoint_config_name,\n", 491 | " ProductionVariants=prod_variants\n", 492 | ")\n", 493 | "\n", 494 | "endpoint_response = sm_client.create_endpoint(\n", 495 | " EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name\n", 496 | ")\n", 497 | "print(f\"Created Endpoint: {endpoint_response['EndpointArn']}\")" 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "id": "bdf730b4-80ca-4553-be16-60d82be22ae1", 503 | "metadata": {}, 504 | "source": [ 505 | "엔드포인트가 생성되는 동안 아래의 문서를 같이 확인해 보세요.\n", 506 | "- https://docs.aws.amazon.com/sagemaker/latest/dg/large-model-inference-dlc.html" 507 | ] 508 | }, 509 | { 510 | "cell_type": "code", 511 | "execution_count": null, 512 | "id": "80136e12-0108-49ce-9689-736fdd4a5602", 513 | "metadata": { 514 | "tags": [] 515 | }, 516 | "outputs": [], 517 | "source": [ 518 | "from IPython.display import display, HTML\n", 519 | "def make_console_link(region, endpoint_name, task='[SageMaker LLM Serving]'):\n", 520 | " endpoint_link = f' {task} Check Endpoint Status' \n", 521 | " return endpoint_link\n", 522 | "\n", 523 | "endpoint_link = make_console_link(region, endpoint_name)\n", 524 | "display(HTML(endpoint_link))" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": null, 530 | "id": "b97b0056-5d95-441f-9c5c-1ec1908008c3", 531 | "metadata": { 532 | "tags": [] 533 | }, 534 | "outputs": [], 535 | "source": [ 536 | "%%time \n", 537 | "from inference_lib import describe_endpoint, Prompter\n", 538 | "describe_endpoint(endpoint_name) " 539 | ] 540 | }, 541 | { 542 | "cell_type": "markdown", 543 | "id": "bf69e880-441f-487c-b458-8bb12df5e0bd", 544 | "metadata": {}, 545 | "source": [ 546 | "
\n", 547 | "\n", 548 | "## 4. Inference\n", 549 | "---\n", 550 | "\n", 551 | "엔드포인트를 호출할 때 이 텍스트를 JSON 페이로드 내에 제공해야 합니다. 이 JSON 페이로드에는 length, sampling strategy, output token sequence restrictions을 제어하는 데 도움이 되는 원하는 추론 매개변수가 포함될 수 있습니다. 허깅페이스 트랜스포머 transformers 라이브러리에는 [사용 가능한 페이로드 매개변수](https://huggingface.co/docs/transformers/main_classes/text_generation#transformers.GenerationConfig)의 전체 목록이 정의되어 있지만, 중요한 페이로드 매개변수는 다음과 같이 정의되어 있습니다:\n", 552 | "\n", 553 | "* **do_sample (`bool`)** – logits sampling 활성화\n", 554 | "* **max_new_tokens (`int`)** – 생성된 토큰의 최대 수\n", 555 | "* **best_of (`int`)** – best_of 개의 시퀀스를 생성하고 가장 높은 토큰 로그 확률이 있는 경우 반환\n", 556 | "* **repetition_penalty (`float`)** – 반복 패널티에 대한 파라미터, 1.0은 패널티가 없음을 의미하여 Greedy 서치와 동일, 커질수록 다양한 결과를 얻을 수 있으며, 자세한 사항은 [this paper](https://arxiv.org/pdf/1909.05858.pdf)을 참고\n", 557 | "* **return_full_text (`bool`)** – 생성된 텍스트에 프롬프트를 추가할지 여부\n", 558 | "* **seed (`int`)** – Random sampling seed\n", 559 | "* **stop_sequences (`List[str]`)** – `stop_sequences` 가 생성되면 토큰 생성을 중지\n", 560 | "* **temperature (`float`)** – logits 분포 모듈화에 사용되는 값\n", 561 | "* **top_k (`int`)** – 상위 K개 만큼 가장 높은 확률 어휘 토큰의 수\n", 562 | "* **top_p (`float`)** – 1 보다 작게 설정하게 되며, 상위부터 정렬했을 때 가능한 토큰들의 확률을 합산하여 `top_p` 이상의 가장 작은 집합을 유지\n", 563 | "* **truncate (`int`)** – 입력 토큰을 지정된 크기로 잘라냄\n", 564 | "* **typical_p (`float`)** – typical Decoding 양으로, 자세한 사항은 [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666)을 참고\n", 565 | "* **watermark (`bool`)** – [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)가 Watermarking\n", 566 | "* **decoder_input_details (`bool`)** – decoder input token logprobs와 ids를 반환" 567 | ] 568 | }, 569 | { 570 | "cell_type": "code", 571 | "execution_count": null, 572 | "id": "ebbb0f5e-3bbd-43d1-b482-9310cc2095f9", 573 | "metadata": { 574 | "tags": [] 575 | }, 576 | "outputs": [], 577 | "source": [ 578 | "params = {\n", 579 | " \"do_sample\": True,\n", 580 | " \"max_new_tokens\": 128,\n", 581 | " \"temperature\": 0.7,\n", 582 | " \"top_p\": 0.9,\n", 583 | " \"return_full_text\": False,\n", 584 | " \"repetition_penalty\": 1.1,\n", 585 | " \"presence_penalty\": None,\n", 586 | " \"eos_token_id\": 2,\n", 587 | "}" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": null, 593 | "id": "40280725-bfbc-4057-9d31-33aec765d606", 594 | "metadata": { 595 | "tags": [] 596 | }, 597 | "outputs": [], 598 | "source": [ 599 | "import json\n", 600 | "from inference_lib import KoLLMSageMakerEndpoint, Prompter\n", 601 | "ep = KoLLMSageMakerEndpoint(endpoint_name)" 602 | ] 603 | }, 604 | { 605 | "cell_type": "code", 606 | "execution_count": null, 607 | "id": "3450e7d7-9c04-44f6-9ca6-d756fc9d3757", 608 | "metadata": { 609 | "tags": [] 610 | }, 611 | "outputs": [], 612 | "source": [ 613 | "\n", 614 | "instruction = \"다음 글을 알기 쉽게 요약해 주세요.\"\n", 615 | "context = \"\"\"\n", 616 | "대규모 언어 모델(LLM; Large Language Models) 분야의 발전과 LLM이 가치 있는 인사이트를 제공하는 문제 세트 수가 계속 증가하고 있다는 소식을 들어보셨을 겁니다. \n", 617 | "대규모 데이터 세트와 여러 작업을 통해 훈련된 대규모 모델은 훈련되지 않은 특정 작업에도 잘 일반화됩니다. 이러한 모델을 파운데이션 모델(foundation model)이라고 하며, 스탠포드 HAI 연구소(Stanford Institute for Human-Centered Artificial Intelligence)에서 처음 대중화한 용어입니다. \n", 618 | "이러한 파운데이션 모델은 프롬프트 엔지니어링(prompt engineering) 기법의 도움으로 잘 활용할 수 있지만, 유스케이스가 도메인에 매우 특화되어 있거나 작업의 종류가 매우 다양하여 모델을 추가로 커스터마이징해야 하는 경우가 많습니다. \n", 619 | "특정 도메인이나 작업에 대한 대규모 모델의 성능을 개선하기 위한 한 가지 접근 방식은 더 작은 작업별 데이터 세트로 모델을 추가로 훈련하는 것입니다. 파인 튜닝(fine-tuning)으로 알려진 이 접근 방식은 LLM의 정확도를 성공적으로 개선하지만, 모든 모델 가중치를 수정해야 합니다. \n", 620 | "파인 튜닝 데이터 세트 크기가 훨씬 작기 때문에 사전 훈련(pre-training)하는 것 보다 훨씬 빠르지만 여전히 상당한 컴퓨팅 성능과 메모리가 필요합니다. \n", 621 | "파인 튜닝은 원본 모델의 모든 파라미터 가중치를 수정하므로 비용이 많이 들고 원본 모델과 동일한 크기의 모델을 생성하므로 스토리지 용량에도 부담이 됩니다.\n", 622 | "\"\"\"\n", 623 | "payload = ep.get_payload(instruction, context, params)" 624 | ] 625 | }, 626 | { 627 | "cell_type": "code", 628 | "execution_count": null, 629 | "id": "1d0706d2-2918-4e4b-8536-ed90134c5d22", 630 | "metadata": { 631 | "tags": [] 632 | }, 633 | "outputs": [], 634 | "source": [ 635 | "%%time\n", 636 | "generated_text = ep.infer(payload, verbose=True)" 637 | ] 638 | }, 639 | { 640 | "cell_type": "markdown", 641 | "id": "1a9a65c8-ec81-4e35-afe0-69e0862c2ac0", 642 | "metadata": {}, 643 | "source": [ 644 | "
\n", 645 | "\n", 646 | "## 5. Clean Up\n", 647 | "---" 648 | ] 649 | }, 650 | { 651 | "cell_type": "code", 652 | "execution_count": null, 653 | "id": "c3dc8bbf-fd0f-4a76-8c39-068fd77e56d0", 654 | "metadata": { 655 | "tags": [] 656 | }, 657 | "outputs": [], 658 | "source": [ 659 | "!rm -rf {local_peft_model_dir} {local_model_dir} serving_src" 660 | ] 661 | }, 662 | { 663 | "cell_type": "code", 664 | "execution_count": null, 665 | "id": "74de01d5-8b2c-43da-bb66-3063a7289cf4", 666 | "metadata": { 667 | "tags": [] 668 | }, 669 | "outputs": [], 670 | "source": [ 671 | "# - Delete the end point\n", 672 | "sm_client.delete_endpoint(EndpointName=endpoint_name)\n", 673 | "# - In case the end point failed we still want to delete the model\n", 674 | "sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name)\n", 675 | "sm_client.delete_model(ModelName=model_name)" 676 | ] 677 | } 678 | ], 679 | "metadata": { 680 | "kernelspec": { 681 | "display_name": "conda_pytorch_p310", 682 | "language": "python", 683 | "name": "conda_pytorch_p310" 684 | }, 685 | "language_info": { 686 | "codemirror_mode": { 687 | "name": "ipython", 688 | "version": 3 689 | }, 690 | "file_extension": ".py", 691 | "mimetype": "text/x-python", 692 | "name": "python", 693 | "nbconvert_exporter": "python", 694 | "pygments_lexer": "ipython3", 695 | "version": "3.10.13" 696 | } 697 | }, 698 | "nbformat": 4, 699 | "nbformat_minor": 5 700 | } 701 | -------------------------------------------------------------------------------- /PEFT/readme.md: -------------------------------------------------------------------------------- 1 | # QLORA: Efficient Finetuning of Quantized LLMs 2 | "QLORA"는 대규모 언어 모델(Large Language Models, LLMs)을 효율적으로 파인튜닝(finetuning)하는 새로운 방법을 제시합니다. 이 방법은 단일 48GB GPU에서 65B 파라미터 모델을 파인튜닝할 수 있을 만큼 메모리 사용을 크게 줄입니다. QL O RA는 4비트로 양자화된 사전 훈련된 언어 모델을 통해 그래디언트를 역전파하여 Low Rank Adapters (LoRA)를 파인튜닝합니다. Amazon SageMaker에서는 Huggingface의 모델을 이용하여 QLORA 파인튜닝을 쉽게 할수 있습니다 3 | 4 | 논문링크 : https://arxiv.org/pdf/2305.14314.pdf 5 | 6 | > **참고:** 본 코드는 SageMaker Notebook Instance에서 구현, 동작 검증 되었습니다. konlpy등 Java가 필요한 패키지들과 로컬 GPU 인스턴스에서 PEFT 파인튜닝하는 과정이 있기 때문입니다. 7 | > 8 | > **테스트 인스턴스 사양:** 9 | > - 타입: ml.g5.2xlarge(A10G 1개) ~ ml.g5.12xlarge(A10G 4개) 10 | > - EBS: 100기가 이상 11 | > 12 | > **코드 실행 순서:** 13 | > 1. 데이터 학습 전처리 14 | > - `1_prepare-dataset-alpaca-method.ipynb` 15 | > - `1_prepare-dataset-chunk-method.ipynb` 16 | > 17 | > 2. 로컬 QLoRA PEFT 학습 18 | > - `2_local-train-debug-lora.ipynb` 19 | > - ml.g5.xlarge 에서 동작 테스트 했습니다. 20 | > 21 | > 3. 로컬 QLoRA PEFT LLM 로드 및 테스트 22 | > - `2_local-infer-debug-lora.ipynb` 23 | > - ml.g5.xlarge 에서 동작 테스트 했습니다. 24 | > 25 | > 4. SageMaker training job - QLoRA PEFT 26 | > - `3_sm-train-lora.ipynb` 27 | > - 코드에 ml.g5.12xlarge에서 학습 인스턴스 설정되어있습니다. 28 | > 29 | > 5. SageMaker Endpoint API 추론배포 - QLoRA PEFT 30 | > - `4_sm-serving-djl.ipynb` 31 | > - 코드는 QLoRA 학습파라미터를 Merge한 원본 파라미터를 사용해서 추론합니다. 32 | > - ml.g5.12xlarge 이상부터 가능합니다. (A10G 4대가 필요합니다.) 33 | 34 | ## 1. Introduction 35 | 대규모 언어 모델(Large Language Models, LLMs)을 파인튜닝(finetuning)하는 것은 그 성능을 향상시키는 매우 효과적인 방법이지만, 매우 큰 모델을 파인튜닝하는 것은 고비용이며 많은 GPU 메모리가 필요합니다. 예를 들어, 65B 파라미터를 가진 LLaMA 모델을 일반적인 16비트 파인튜닝으로 훈련시키려면 780GB 이상의 GPU 메모리가 필요합니다. 이러한 문제를 해결하기 위해 QL O RA 방법이 제안되었습니다. 이 방법은 4비트로 양자화된 모델을 파인튜닝하면서 성능 저하 없이 메모리 사용량을 크게 줄입니다. 이를 통해 단일 GPU에서도 대규모 모델을 파인튜닝할 수 있게 되어, 이러한 모델의 접근성이 크게 향상됩니다. 36 | 37 | 38 | ## 2. QLORA 이해를 위한 백그라운드 39 | 40 | ### 2-a.Block-wise k-bit Quantization 41 | 42 | #### 개념 43 | 44 | - **양자화(Quantization)**: 입력 데이터를 더 적은 정보를 가진 표현으로 변환하는 과정입니다. 일반적으로 더 많은 비트를 가진 데이터 타입을 더 적은 비트로 변환합니다. 예를 들어, 32-bit 부동소수점을 4-bit 정수로 변환할 수 있습니다. 45 | 46 |

47 | image 1 48 |
49 | What is Quantization in the signal? 50 |
51 | 52 | 53 | #### 기본 방법 54 | 55 | - **정규화(Normalization)**: 낮은 비트 데이터 타입의 전체 범위가 사용되도록 입력 데이터 타입은 대상 데이터 타입 범위로 정규화됩니다. 이는 입력 요소의 절대 최대값으로 정규화하여 이루어집니다. 56 | - **양자화 상수(Quantization Constant)**: 이 상수는 양자화 과정에서 사용되며, 일반적으로 $c$로 표시됩니다. 57 | - For example, quantizing a 32-bit Floating Point (FP32) tensor into a Int8 tensor with range $[−127, 127]$ 58 | 59 |
60 | image 2 61 |
62 | 63 | 64 | 65 | 66 | #### 문제점 67 | 68 | - **아웃라이어(Outlier)**: 입력에 큰 크기의 값이 있을 경우, 양자화 빈(quantization bins)이 잘 활용되지 않을 수 있습니다. 69 | #### 해결 방법 70 | 71 | - **블록별 양자화(Block-wise Quantization)**: 이 문제를 해결하기 위해 입력 텐서를 독립적으로 양자화되는 블록으로 분할합니다. 각 블록은 자체 양자화 상수 $\( c \)$를 가집니다. 72 | 73 | - **블록 크기(Block Size)**: 입력 텐서는 $\( B \)$ 크기의 블록으로 분할되며, 이러한 블록은 독립적으로 양자화됩니다. 74 | 75 | 76 | ### 2-b. Low-rank Adapters (LoRA) 77 | 78 | **Low-rank Adapters (LoRA)**는 파인튜닝(finetuning) 과정에서 메모리 요구 사항을 줄이기 위한 방법입니다. 이 방법은 작은 수의 훈련 가능한 매개변수, 즉 어댑터를 사용하면서 전체 모델 매개변수는 고정된 상태로 둡니다. 79 |
80 | image 3 81 |
82 | 83 | #### 작동 원리 84 | - 확률적 경사 하강법(stochastic gradient descent) 동안 그래디언트는 고정된 사전 훈련된 모델 가중치를 통과하여 어댑터로 전달됩니다. 85 | - 이 어댑터는 손실 함수를 최적화하기 위해 업데이트됩니다. 86 | 87 | 88 | LoRA는 선형 투영(linear projection)을 추가적인 팩터화된 투영(factorized projection)으로 확장합니다. 주어진 투영 $XW = Y$이 있을 때, $\( X \in R^{b \times h}$, $W \in R^{h \times o} \)$, LoRA는 다음과 같이 계산됩니다: 89 | 90 | $$Y = XW + sXL_1 L_2 ,$$ 91 | 92 | 여기서 $\( L_1 \in R^{h \times r} \)$와 $\( L_2 \in R^{r \times o} \)$이고, $\( s \)$는 스칼라입니다. 93 | 94 | #### 메모리 효율성 95 | - LoRA의 메모리 요구 사항은 매우 작기 때문에, 더 많은 어댑터를 사용하여 성능을 향상시킬 수 있습니다. 96 | - 이는 전체 메모리 사용량을 크게 증가시키지 않으면서도 가능합니다. 97 | 98 | 99 | ### 2-c. Memory Requirement of Parameter-Efficient Finetuning 100 | 101 | 논문에서는 Parameter Efficient Finetuning (PEFT) 방법을 사용할 때 대부분의 메모리 사용량이 활성화 그래디언트(activation gradients)에서 발생하며, LoRA 매개변수에서는 상대적으로 적은 메모리만 사용한다고 설명하고 있습니다. 102 | 103 | 예를 들어, 7B LLaMA 모델을 FLAN v2 데이터셋과 배치 크기 1로 훈련할 때, LoRA 가중치는 원래 모델 가중치의 0.2%에 해당합니다. 이 경우, LoRA 입력 그래디언트는 567 MB의 메모리를 차지하는 반면, LoRA 매개변수는 단지 26 MB만 차지합니다. 104 | 105 | 그래디언트 체크포인팅을 사용하면 입력 그래디언트의 메모리 사용량은 시퀀스당 평균 18 MB로 줄어들며, 이는 모든 LoRA 가중치의 메모리 사용량보다 더 많습니다. 이를 통해 그래디언트 체크포인팅이 중요하다는 것과 함께, LoRA 매개변수의 양을 크게 줄이는 것은 메모리 효율성에 큰 영향을 미치지 않는다는 것을 강조하고 있습니다. 106 | 107 | 이 정보는 더 많은 어댑터를 사용하면서도 전체 훈련 메모리 사용량을 크게 증가시키지 않을 수 있음을 의미합니다. 108 | 109 | 110 | 111 | ## 3. QLORA Finetuning의 핵심 3가지 112 | > 참고: 이 정보는 QLORA의 효율성과 성능을 높이는 데 중요한 역할을 합니다. 113 |
114 | image 4 115 |
116 | QLORA는 LoRA를 개선하기 위해 트랜스포머 모델을 4비트 정밀도로 양자화하고, 메모리 스파이크를 처리하기 위해 페이지드 옵티마이저를 사용합니다. 117 | 이는 기본적으로 QLORA가 LoRA에 비해 메모리 사용량을 줄이면서도 성능을 유지하거나 향상시키는 방법을 제시한다는 것을 의미합니다. 4비트 정밀도로 모델을 양자화하면 메모리 사용량이 줄어들고, 페이지드 옵티마이저는 메모리 사용량이 급증하는 상황을 효과적으로 관리해줍니다. 118 | 119 | ### 3-a. 4-bit NormalFloat 양자화 120 | 121 | 4-bit NormalFloat 양자화는 QLORA에서 중요한 구성 요소로, 정보 이론적으로 최적으로 설계되었습니다. 이는 4-bit 정수(Int4)와 4-bit 실수(FP4)에 비해 더 나은 경험적 결과를 제공합니다. 122 | 123 | #### 작동 원리 124 | NormalFloat (NF)는 Quantile 양자화에 기반을 두고 있으며, 이는 정보 이론적으로 최적의 데이터 유형입니다. QLORA의 가중치 텐서 $\( W \)$가 사용될 때, 이 텐서는 BFloat16으로 역양자화되어 $\( W' \)$가 됩니다. 그 후, 16-bit에서 행렬 곱셈이 수행됩니다: 125 | 126 | $\[ 127 | Y = X \times W' 128 | \]$ 129 | 130 | 여기서 $\( Y \)$는 출력 텐서, $\( X \)$는 입력 텐서, $\( W' \)$는 역양자화된 가중치 텐서입니다. 131 | 132 | #### 성능 133 | 논문의 실험 결과에 따르면, 4-bit NormalFloat (NF4)는 4-bit 실수 (FP4)와 4-bit 정수 (Int4)보다 더 나은 성능을 보입니다. 또한, 이 데이터 유형은 16-bit 전체 파인튜닝 및 16-bit LoRA 파인튜닝 성능과 일치한다고 합니다. 134 | 135 | #### 메모리 절약 136 | 4-bit NormalFloat는 메모리 사용량을 줄이면서도 성능을 유지하거나 향상시키는 데 도움이 됩니다. 이는 특히 메모리 제한이 있는 환경에서 유용합니다. 137 | 138 | 139 | ### 3-b. Double Quantization 140 | 141 | #### 개요 142 | Double Quantization은 QLORA의 중요한 구성 요소 중 하나로, 양자화 상수를 다시 양자화하는 방법입니다. 이는 평균적으로 매개변수 당 약 0.37비트를 절약하며, 65B 모델에 대해 약 3GB의 메모리를 절약할 수 있습니다. 143 | 144 | #### 작동 원리 145 | Double Quantization은 기본적으로 양자화 상수 자체를 양자화하는 과정입니다. 일반적인 양자화에서는 데이터를 양자화하여 메모리를 절약하지만, 양자화 상수는 여전히 저장되어 있어야 합니다. Double Quantization은 이러한 양자화 상수까지도 양자화하여 추가적인 메모리 절약을 달성합니다. 146 | 147 | #### 성능 148 | 논문의 실험 결과에 따르면, Double Quantization은 성능을 저하시키지 않으면서 메모리를 효과적으로 절약합니다. 이는 4-bit QLORA가 16-bit 전체 파인튜닝 및 16-bit LoRA 파인튜닝 성능과 일치한다는 것을 뒷받침합니다. 149 | 150 | #### 메모리 절약 151 | Double Quantization은 평균적으로 매개변수 당 약 0.37비트를 절약합니다. 이는 큰 모델, 특히 65B 모델에 대해 약 3GB의 메모리를 절약할 수 있습니다. 152 | 153 | 154 | ### 3-c. Paged Optimizers 155 | 156 | #### 개요 157 | Paged Optimizers는 NVIDIA의 통합 메모리 기능을 활용하여 긴 시퀀스 길이의 미니배치를 처리할 때 발생하는 그래디언트 체크포인팅 메모리 스파이크를 피하는 기술입니다. 158 | 159 | #### 작동 원리 160 | Paged Optimizers는 NVIDIA의 통합 메모리를 사용하여 CPU와 GPU 사이의 페이지-페이지 전송을 자동으로 수행합니다. 이 기능은 GPU가 메모리를 초과할 경우 오류 없이 처리를 계속할 수 있게 해줍니다. 최적화기의 상태에 대한 페이지 메모리를 할당하고, GPU가 메모리를 초과하면 이를 CPU RAM으로 자동 이동시킵니다. 필요한 경우, 최적화기 업데이트 단계에서 다시 GPU 메모리로 페이지를 되돌립니다. 161 | 162 | #### 성능 163 | 논문에 따르면, Paged Optimizers는 65B 모델에 대해 48GB GPU에서 배치 크기가 16일 때 일반 최적화기와 동일한 훈련 속도를 제공합니다. 그러나 이 기술이 언제 느려질지에 대한 측정은 미래의 작업에서 이루어져야 합니다. 164 | 165 | #### 메모리 관리 166 | 이 기술은 특히 33B/65B 크기의 QLORA 모델을 단일 24/48GB GPU에서 튜닝할 때 중요합니다. 긴 시퀀스 길이의 미니배치를 처리할 때만 페이징이 발생하기 때문에, 이는 드문 경우입니다. 167 | 168 |
169 | image 8 170 |
171 | 172 | ##### 참조 173 | - 그래디언트 체크포인팅(Gradient Checkpointing) 174 | 175 | 딥 러닝 모델을 훈련할 때 메모리 사용량을 줄이기 위한 기술입니다. 일반적으로 딥 러닝 모델을 훈련시키려면 각 계층에서의 중간 출력값을 저장해야 합니다. 이 중간 출력값은 역전파(Backpropagation) 과정에서 그래디언트를 계산할 때 필요합니다. 그러나 이러한 중간 출력값을 모두 저장하면 많은 양의 메모리가 필요하게 됩니다. 176 | 그래디언트 체크포인팅은 이러한 중간 출력값을 일부만 저장하고, 나머지는 필요할 때 다시 계산하는 방식으로 메모리 사용량을 줄입니다. 이로 인해 계산 시간은 약간 늘어날 수 있지만, 메모리 사용량은 크게 줄어듭니다. 177 | 178 | - 메모리 스파이크(Memory Spike) 179 | 180 | 그러나 그래디언트 체크포인팅을 사용하면 특정 시점에서 메모리 사용량이 급격히 증가하는 현상, 즉 "메모리 스파이크"가 발생할 수 있습니다. 이는 특히 긴 시퀀스를 처리할 때 더욱 두드러집니다. 이러한 메모리 스파이크 때문에 훈련 과정이 중단될 수 있으므로, 이를 관리하는 방법이 필요합니다. 181 | 182 | ## 4. QLoRA vs. Standard Finetuning 183 | 184 | ### 4-a. Default LoRA Hyperparameters Do Not Match 16-bit Performance 185 | 186 | LoRA (Low-rank Adapters)의 기본 하이퍼파라미터 설정은 16비트 설정과 일치하지 않습니다. 187 | 188 | #### 주요 내용 189 | 논문에서는 LoRA의 기본 하이퍼파라미터 설정이 큰 기본 모델에 대한 전체 파인튜닝 성능을 복제할 수 없다는 것을 보여줍니다. 따라서, LoRA를 모든 트랜스포머 계층에 적용하는 것이 중요하며, 이를 통해 16비트 스텐다드 성능을 달성할 수 있다고 주장함. 190 | 191 | 1. **LoRA 어댑터의 중요성** 192 | - 논문에서는 LoRA 어댑터의 수가 얼마나 중요한지, 그리고 모든 선형 트랜스포머 블록 계층에서 LoRA가 필요한지를 논의합니다. 이는 Alpaca 데이터셋에서 LLaMA 7B 모델을 파인튜닝할 때의 결과를 통해 확인되었습니다. 193 | 194 | 2. **다른 하이퍼파라미터의 영향** 195 | - 논문에서는 다른 LoRA 하이퍼파라미터, 예를 들어 투영 차원 $\( r \)$,이 성능에 미치는 영향이 없다고 언급합니다 (부록 A 참조). 196 | 197 |
198 | image 4 199 |
200 | 201 | - LoRA dropout : 은 0.0, 0.05, 0.1 중에서 선택할 수 있습니다. 작은 모델(7B, 13B)에 대해서는 LoRA dropout 0.05가 유용하다고 판단되었습니다. 202 | - 그러나 더 큰 모델(33B, 65B)에 대해서는 LoRA dropout 0.05가 유용하지 않았습니다.또한, 모델 크기가 최대 13B까지는 LoRA dropout을 0.1로 설정하고, 33B와 65B 모델에 대해서는 0.05로 설정했습니다. 203 | - 이 정보를 기반으로 모델의 크기와 성능에 따라 LoRA dropout 값을 조정할 수 있음을 나타냅니다. 이는 과적합을 방지하고 모델의 일반화 성능을 향상시키는 데 도움이 된다고 판단할수 있습니다. 204 | 205 | 3. **16비트 성능과의 비교** 206 | - 논문에서는 LoRA를 모든 트랜스포머 계층에 적용할 경우에만 16비트 성능과 일치한다고 설명 207 | 208 | 209 | 210 | ### 4-b. 4-bit NormalFloat은 4-bit Floating Point보다 더 나은 성능을 보임 211 | 212 | 4비트 NormalFloat (NF4)가 표준 4비트 부동 소수점 (FP4)보다 어떤 실증적 이점이 있는지에 대해 논의합니다. 213 |
214 | image 5 215 |
216 | 217 | #### 주요 포인트 218 | 219 | 1. **정보 이론적 최적성** 220 | - 4비트 NormalFloat (NF4) 데이터 유형은 정보 이론적으로 최적이라고 설명되어 있습니다. 이는 데이터를 가장 효율적으로 표현하기 위해 설계되었습니다. 221 | 222 | 2. **실증적 이점** 223 | - 논문은 언어 모델링과 일련의 제로샷 작업에 대한 다양한 데이터 유형의 성능을 평가하기 위해 이전 연구의 설정을 따릅니다. 결과적으로 NF4가 FP4와 Int4보다 성능을 크게 향상시킨다고 나와 있습니다. 224 | 225 | 3. **메모리 사용량** 226 | - 논문에서는 더블 양자화가 성능을 저하시키지 않으면서 메모리 사용량을 줄인다고 언급합니다. 227 | 228 | 4. **16비트와의 비교** 229 | - 논문은 NF4와 더블 양자화가 16비트 성능을 완전히 회복한다고 덧붙여, NF4가 양자화 정밀도 측면에서 FP4보다 우수하다고 주장합니다. 230 | 231 | 5. **일관된 결과** 232 | - 논문은 4비트 QLoRA와 NF4 데이터 유형이 학술적 벤치마크에서 16비트 전체 파인튜닝 및 16비트 LoRA 파인튜닝 성능과 일치한다고 요약합니다. 233 | 234 | 235 | 논문은 4비트 NormalFloat가 특히 QLoRA 파인튜닝의 맥락에서 4비트 부동 소수점보다 더 효과적인 데이터 유형이라고 제시합니다. 236 | 예를 들어, 언어 모델링 작업에서 NF4를 사용하면 FP4를 사용할 때보다 더 높은 정확도를 달성할 수 있습니다. 이는 NF4가 데이터를 더 효율적으로 표현하고, 따라서 더 나은 성능을 제공하기 때문입니다. 이러한 차이점은 NF4를 더 효과적인 데이터 유형으로 만들며, 특히 QLoRA 파인튜닝과 같은 상황에서는 FP4보다 더 우수한 성능을 보인다고 제시. 237 | 238 | 논문에서 제시한 Appendix E의 NormalFloat 4bit을 보면, 주어진 값은 일반적인 부동소수점 (Floating Point)과 다르게, 특정한 분포나 규칙을 따르는것으로 보입니다. 239 | 일반적인 부동소수점은 균일한 간격으로 값을 표현하지만, NormalFloat의 특징은 아래와 같습니다. 240 | 4-bit NormalFloat는 정보이론적인 최적화된 양자화 방식으로 일반적인 부동소수점과는 다르게, 데이터의 특정 분포나 특성을 반영한다고 논문에서 제사합니다. 241 | 242 | - 비대칭 분포: 값들이 0을 중심으로 비대칭적으로 분포되어 있습니다. 243 | - 비선형 간격: 값들 사이의 간격이 일정하지 않고, 비선형적입니다. 이는 일반적인 부동소수점에서는 보기 힘든 특성입니다. 244 | - 한정된 범위: 값들이 -1.0에서 1.0 사이에 한정되어 있습니다. 245 | 246 | 247 | #### Appendix E 248 | - NormalFloat 4-bit data type 249 | The exact values of the NF4 data type are as follows: 250 | $$[-1.0, -0.6961928009986877, -0.5250730514526367, 251 | -0.39491748809814453, -0.28444138169288635, -0.18477343022823334, 252 | -0.09105003625154495, 0.0, 0.07958029955625534, 0.16093020141124725, 253 | 0.24611230194568634, 0.33791524171829224, 0.44070982933044434, 254 | 0.5626170039176941, 0.7229568362236023, 1.0]$$ 255 | 256 | 257 | ### 4-c. k-bit QLORA는 16-bit 전체 파인튜닝 및 16-bit LoRA 성능과 일치 258 | 259 | 논문에서는 k-bit QLORA가 16-bit 전체 파인튜닝과 16-bit LoRA 파인튜닝 성능과 일치한다고 주장합니다. 이는 학술적 벤치마크에서의 실험 결과를 통해 확인하고, 260 | k-bit QLORA는 더 적은 메모리와 연산을 사용하면서도 16-bit의 전체 파인튜닝과 LoRA 파인튜닝 성능과 일치하는 높은 성능을 보이는 것으로 확인되었습니다. 이는 k-bit QLORA의 효율성과 범용성을 강조했습니다. 261 | 262 | #### 주요 포인트 263 | 264 | 1. **성능 일치** 265 | - k-bit QLORA는 16-bit의 전체 파인튜닝과 LoRA 파인튜닝 성능과 일치하는 높은 성능을 보입니다. 논문의 벤치마크에서 일치한다고 주장함. 266 | 267 | 2. **효율성** 268 | - k-bit QLORA는 더 적은 비트를 사용하면서도 16-bit 성능을 달성합니다. 이는 메모리와 연산 효율성 측면에서 중요한 의미를 가집니다. 269 | 270 | 3. **범용성** 271 | - 이 결과는 다양한 작업과 데이터셋에서 일관되게 나타나, k-bit QLORA의 범용성을 입증합니다. 272 | 273 | ### 4-d. 요약 274 | 275 | #### 주요 내용: 276 | 논문은 4-bit QLORA가 높은 성능을 유지하면서도 메모리와 연산 효율성을 향상시킬 수 있다는 중요한 포인트를 강조합니다. 277 | 1. **성능 일치** 278 | - 4-bit QLORA는 NF4 데이터 유형을 사용하여 16-bit 전체 파인튜닝과 16-bit LoRA 파인튜닝 성능과 일치합니다. 이는 학술적 벤치마크에서 잘 정립된 평가 설정을 사용하여 설명합니다. 279 | 280 | 2. **NF4의 효과성** 281 | - NF4 데이터 유형이 FP4보다 더 효과적이라고 제시 282 | 283 | 3. **더블 양자화의 효과** 284 | - 논문은 더블 양자화(double quantization)가 성능을 저하시키지 않는다는 것을 소개 285 | 286 | 4. **종합적 효과** 287 | - 논문에서는 이러한 결과들은 4-bit QLORA 파인튜닝이 16-bit 방법과 일치하는 결과를 안정적으로 가져온다는 결론을 제시합니다. 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /PEFT/src/local-run-wandb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | pip install -r requirements.txt 5 | 6 | mkdir -p /tmp/huggingface-cache/ 7 | export HF_DATASETS_CACHE="/tmp/huggingface-cache" 8 | TIMESTAMP=$(date +"%Y-%m-%d-%T") 9 | 10 | declare -a OPTS=( 11 | --base_model nlpai-lab/kullm-polyglot-12.8b-v2 12 | --pretrained_model_path /home/ec2-user/SageMaker/models/kullm-polyglot-12-8b-v2/ 13 | --cache_dir $HF_DATASETS_CACHE 14 | --data_path ../chunk-train 15 | --output_dir output 16 | --chkpt_dir chkpt 17 | --save_path ./model 18 | --batch_size 2 19 | --gradient_accumulation_steps 2 20 | --num_epochs 1 21 | --learning_rate 3e-4 22 | --lora_r 8 23 | --lora_alpha 16 24 | --lora_dropout 0.05 25 | --lora_target_modules "[query_key_value, xxx]" 26 | --logging_steps 1 27 | --save_steps 40 28 | --eval_steps 40 29 | --weight_decay 0. 30 | --warmup_steps 50 31 | --warmup_ratio 0.03 32 | --lr_scheduler_type "constant" 33 | --wandb_project "sagemaker-training" 34 | --wandb_run_name "qlora-"$TIMESTAMP 35 | --wandb_watch "false" 36 | ) 37 | 38 | NUM_GPUS=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) 39 | 40 | if [ $NUM_GPUS -eq 1 ] 41 | then 42 | echo python train.py "${OPTS[@]}" "$@" 43 | CUDA_VISIBLE_DEVICES=0 python train.py "${OPTS[@]}" "$@" 44 | else 45 | echo torchrun --nnodes 1 --nproc_per_node "$NUM_GPUS" train.py "${OPTS[@]}" "$@" 46 | torchrun --nnodes 1 --nproc_per_node "$NUM_GPUS" train.py "${OPTS[@]}" "$@" 47 | fi -------------------------------------------------------------------------------- /PEFT/src/local-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | pip install -r requirements.txt 5 | 6 | mkdir -p /tmp/huggingface-cache/ 7 | export HF_DATASETS_CACHE="/tmp/huggingface-cache" 8 | 9 | declare -a OPTS=( 10 | --base_model nlpai-lab/kullm-polyglot-12.8b-v2 11 | --pretrained_model_path /home/ec2-user/SageMaker/models/kullm-polyglot-12-8b-v2/ 12 | --cache_dir $HF_DATASETS_CACHE 13 | --data_path ../chunk-train 14 | --output_dir output 15 | --chkpt_dir chkpt 16 | --save_path ./model 17 | --batch_size 2 18 | --gradient_accumulation_steps 2 19 | --num_epochs 1 20 | --learning_rate 3e-4 21 | --lora_r 8 22 | --lora_alpha 16 23 | --lora_dropout 0.05 24 | --lora_target_modules "[query_key_value, xxx]" 25 | --logging_steps 1 26 | --save_steps 40 27 | --eval_steps 40 28 | --weight_decay 0. 29 | --warmup_steps 50 30 | --warmup_ratio 0.03 31 | --lr_scheduler_type "linear" 32 | ) 33 | 34 | NUM_GPUS=$(nvidia-smi --query-gpu=name --format=csv,noheader | wc -l) 35 | 36 | if [ $NUM_GPUS -eq 1 ] 37 | then 38 | echo python train.py "${OPTS[@]}" "$@" 39 | CUDA_VISIBLE_DEVICES=0 python train.py "${OPTS[@]}" "$@" 40 | else 41 | echo torchrun --nnodes 1 --nproc_per_node "$NUM_GPUS" train.py "${OPTS[@]}" "$@" 42 | torchrun --nnodes 1 --nproc_per_node "$NUM_GPUS" train.py "${OPTS[@]}" "$@" 43 | fi -------------------------------------------------------------------------------- /PEFT/src/requirements.txt: -------------------------------------------------------------------------------- 1 | peft==0.7.1 2 | accelerate==0.25.0 3 | transformers==4.36.2 4 | bitsandbytes==0.41.3 5 | datasets==2.16.0 6 | wandb 7 | -------------------------------------------------------------------------------- /PEFT/src/run-wandb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | pip install -r requirements.txt 5 | 6 | mkdir -p /tmp/huggingface-cache/ 7 | export HF_DATASETS_CACHE="/tmp/huggingface-cache" 8 | TIMESTAMP=$(date +"%Y-%m-%d-%T") 9 | 10 | declare -a OPTS=( 11 | --base_model nlpai-lab/kullm-polyglot-12.8b-v2 12 | --pretrained_model_path /opt/ml/input/data/pretrained/ 13 | --cache_dir $HF_DATASETS_CACHE 14 | --data_path /opt/ml/input/data/training/ 15 | --output_dir /opt/ml/checkpoints 16 | --save_path /opt/ml/model/ 17 | --batch_size 2 18 | --gradient_accumulation_steps 2 19 | --num_epochs 1 20 | --learning_rate 3e-4 21 | --lora_r 8 22 | --lora_alpha 32 23 | --lora_dropout 0.05 24 | --lora_target_modules "[query_key_value, xxx]" 25 | --logging_steps 1 26 | --save_steps 40 27 | --eval_steps 40 28 | --weight_decay 0. 29 | --warmup_steps 50 30 | --warmup_ratio 0.03 31 | --lr_scheduler_type "linear" 32 | --wandb_project "sagemaker-training" 33 | --wandb_run_name "qlora-"$TIMESTAMP 34 | --wandb_watch "false" 35 | ) 36 | 37 | if [ $SM_NUM_GPUS -eq 1 ] 38 | then 39 | echo python train.py "${OPTS[@]}" "$@" 40 | CUDA_VISIBLE_DEVICES=0 python train.py "${OPTS[@]}" "$@" 41 | else 42 | echo torchrun --nnodes 1 --nproc_per_node "$SM_NUM_GPUS" train.py "${OPTS[@]}" "$@" 43 | torchrun --nnodes 1 --nproc_per_node "$SM_NUM_GPUS" train.py "${OPTS[@]}" "$@" 44 | fi -------------------------------------------------------------------------------- /PEFT/src/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | pip install -r requirements.txt 5 | 6 | mkdir -p /tmp/huggingface-cache/ 7 | export HF_DATASETS_CACHE="/tmp/huggingface-cache" 8 | 9 | declare -a OPTS=( 10 | --base_model nlpai-lab/kullm-polyglot-12.8b-v2 11 | --pretrained_model_path /opt/ml/input/data/pretrained/ 12 | --cache_dir $HF_DATASETS_CACHE 13 | --data_path /opt/ml/input/data/training/ 14 | --output_dir /opt/ml/checkpoints 15 | --save_path /opt/ml/model/ 16 | --batch_size 2 17 | --gradient_accumulation_steps 2 18 | --num_epochs 1 19 | --learning_rate 3e-4 20 | --lora_r 8 21 | --lora_alpha 32 22 | --lora_dropout 0.05 23 | --lora_target_modules "[query_key_value, xxx]" 24 | --logging_steps 1 25 | --save_steps 40 26 | --eval_steps 40 27 | --weight_decay 0. 28 | --warmup_steps 50 29 | --warmup_ratio 0.03 30 | --lr_scheduler_type "linear" 31 | ) 32 | 33 | if [ $SM_NUM_GPUS -eq 1 ] 34 | then 35 | echo python train.py "${OPTS[@]}" "$@" 36 | CUDA_VISIBLE_DEVICES=0 python train.py "${OPTS[@]}" "$@" 37 | else 38 | echo torchrun --nnodes 1 --nproc_per_node "$SM_NUM_GPUS" train.py "${OPTS[@]}" "$@" 39 | torchrun --nnodes 1 --nproc_per_node "$SM_NUM_GPUS" train.py "${OPTS[@]}" "$@" 40 | fi -------------------------------------------------------------------------------- /PEFT/src/secrets.env: -------------------------------------------------------------------------------- 1 | WANDB_API_KEY= 2 | -------------------------------------------------------------------------------- /PEFT/src/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from typing import List 4 | import argparse 5 | import torch 6 | import transformers 7 | from datasets import load_dataset, load_from_disk 8 | from transformers import BitsAndBytesConfig 9 | from pathlib import Path 10 | from huggingface_hub import snapshot_download 11 | 12 | from peft import ( 13 | LoraConfig, 14 | get_peft_model, 15 | get_peft_model_state_dict, 16 | prepare_model_for_kbit_training, 17 | set_peft_model_state_dict, 18 | ) 19 | from transformers import GPTNeoXForCausalLM, GPTNeoXTokenizerFast 20 | 21 | def parse_args(): 22 | """Parse the arguments.""" 23 | parser = argparse.ArgumentParser() 24 | 25 | # path 26 | parser.add_argument("--base_model", type=str, default="nlpai-lab/kullm-polyglot-12.8b-v2", help="Model id to use for training.") 27 | parser.add_argument("--cache_dir", type=str, default=os.environ["HF_DATASETS_CACHE"]) 28 | parser.add_argument("--pretrained_model_path", type=str, default=None) 29 | parser.add_argument("--data_path", type=str, default="../data/kkulm-v2", help="Path to dataset.") 30 | parser.add_argument("--output_dir", type=str, default="./lora-alpaca") 31 | parser.add_argument("--save_path", type=str, default="./model") 32 | parser.add_argument("--save_merged_model", type=bool, default=False) 33 | 34 | # add training hyperparameters 35 | parser.add_argument("--batch_size", type=int, default=1, help="Batch size to use for training.") 36 | parser.add_argument("--num_epochs", type=int, default=1) 37 | parser.add_argument("--learning_rate", type=float, default=2e-5, help="Learning rate to use for training.") 38 | parser.add_argument("--gradient_accumulation_steps", type=int, default=2) 39 | parser.add_argument("--lr_scheduler_type", type=str, default="linear") 40 | 41 | # quantization parameters 42 | parser.add_argument("--quant_8bit", type=bool, default=False) 43 | parser.add_argument("--quant_4bit", type=bool, default=True) 44 | 45 | # lora hyperparams 46 | parser.add_argument("--lora_r", type=int, default=8) 47 | parser.add_argument("--lora_alpha", type=int, default=32) 48 | parser.add_argument("--lora_dropout", type=float, default=0.05) 49 | 50 | # llm hyperparams 51 | parser.add_argument("--group_by_length", type=bool, default=False) 52 | 53 | # wandb params 54 | parser.add_argument("--wandb_project", type=str, default="") 55 | parser.add_argument("--wandb_run_name", type=str, default="") 56 | parser.add_argument("--wandb_watch", type=str, default="") # options: false | gradients | all 57 | parser.add_argument("--wandb_log_model", type=str, default="") # options: false | true 58 | 59 | parser.add_argument("--save_steps", type=int, default=200) 60 | parser.add_argument("--eval_steps", type=int, default=200) 61 | 62 | parser.add_argument( 63 | "--bf16", 64 | type=bool, 65 | default=True if torch.cuda.get_device_capability()[0] == 8 else False, 66 | help="Whether to use bf16.", 67 | ) 68 | args = parser.parse_known_args() 69 | return args 70 | 71 | 72 | def train(args): 73 | base_model = args.base_model 74 | cache_dir = args.cache_dir 75 | pretrained_model_path = args.pretrained_model_path 76 | data_path = args.data_path 77 | output_dir = args.output_dir 78 | save_path = args.save_path 79 | save_merged_model = args.save_merged_model 80 | batch_size = args.batch_size 81 | num_epochs = args.num_epochs 82 | learning_rate = args.learning_rate 83 | gradient_accumulation_steps = args.gradient_accumulation_steps 84 | lr_scheduler_type = args.lr_scheduler_type 85 | quant_8bit = args.quant_8bit 86 | quant_4bit = args.quant_4bit 87 | lora_r = args.lora_r 88 | lora_alpha = args.lora_alpha 89 | lora_dropout = args.lora_dropout 90 | group_by_length = args.group_by_length 91 | wandb_project = args.wandb_project 92 | wandb_run_name = args.wandb_run_name 93 | wandb_watch = args.wandb_watch 94 | wandb_log_model = args.wandb_log_model 95 | save_steps = args.save_steps 96 | eval_steps = args.eval_steps 97 | bf16 = args.bf16 98 | 99 | if int(os.environ.get("LOCAL_RANK", 0)) == 0: 100 | print( 101 | f"Training Alpaca-LoRA model with params:\n" 102 | f"base_model: {base_model}\n" 103 | f"cache_dir: {cache_dir}\n" 104 | f"pretrained_model_path: {pretrained_model_path}\n" 105 | f"data_path: {data_path}\n" 106 | f"output_dir: {output_dir}\n" 107 | f"save_path: {save_path}\n" 108 | f"save_merged_model: {save_merged_model}\n" 109 | f"batch_size: {batch_size}\n" 110 | f"num_epochs: {num_epochs}\n" 111 | f"learning_rate: {learning_rate}\n" 112 | f"gradient_accumulation_steps: {gradient_accumulation_steps}\n" 113 | f"lr_scheduler_type: {lr_scheduler_type}\n" 114 | f"quant_8bit: {quant_8bit}\n" 115 | f"quant_4bit: {quant_4bit}\n" 116 | f"lora_r: {lora_r}\n" 117 | f"lora_alpha: {lora_alpha}\n" 118 | f"lora_dropout: {lora_dropout}\n" 119 | f"group_by_length: {group_by_length}\n" 120 | f"wandb_project: {wandb_project}\n" 121 | f"wandb_run_name: {wandb_run_name}\n" 122 | f"wandb_watch: {wandb_watch}\n" 123 | f"wandb_log_model: {wandb_log_model}\n" 124 | f"save_steps: {save_steps}\n" 125 | f"eval_steps: {eval_steps}\n" 126 | ) 127 | assert base_model, "Please specify a --base_model, e.g. --base_model='huggyllama/llama-7b'" 128 | 129 | os.makedirs(output_dir, exist_ok=True) 130 | device_map = "auto" 131 | world_size = int(os.environ.get("WORLD_SIZE", 1)) 132 | print(f"world_size: {world_size}") 133 | 134 | ddp = world_size != 1 135 | if ddp: 136 | device_map = {"": int(os.environ.get("LOCAL_RANK") or 0)} 137 | print("Activated Distributed Data Parallel.") 138 | #gradient_accumulation_steps = gradient_accumulation_steps // world_size 139 | 140 | # Check if parameter passed or if set within environ 141 | use_wandb = len(wandb_project) > 0 or ("WANDB_PROJECT" in os.environ and len(os.environ["WANDB_PROJECT"]) > 0) 142 | 143 | # Only overwrite environ if wandb param passed 144 | if len(wandb_project) > 0: 145 | os.environ["WANDB_PROJECT"] = wandb_project 146 | if len(wandb_watch) > 0: 147 | os.environ["WANDB_WATCH"] = wandb_watch 148 | if len(wandb_log_model) > 0: 149 | os.environ["WANDB_LOG_MODEL"] = wandb_log_model 150 | 151 | if quant_4bit: 152 | nf4_config = BitsAndBytesConfig( 153 | load_in_4bit=True, 154 | bnb_4bit_quant_type="nf4", 155 | bnb_4bit_use_double_quant=True, 156 | bnb_4bit_compute_dtype=torch.bfloat16 157 | ) 158 | else: 159 | nf4_config = None 160 | 161 | tokenizer = GPTNeoXTokenizerFast.from_pretrained(pretrained_model_path) 162 | tokenizer.pad_token_id = 0 # unk. we want this to be different from the eos token 163 | tokenizer.padding_side = "left" # Allow batched inference 164 | 165 | model = GPTNeoXForCausalLM.from_pretrained( 166 | #base_model, 167 | pretrained_model_path, 168 | load_in_8bit=True if quant_8bit else False, 169 | torch_dtype=torch.float16, 170 | device_map=device_map, 171 | cache_dir=cache_dir, 172 | quantization_config=nf4_config, 173 | ) 174 | 175 | model = prepare_model_for_kbit_training(model) 176 | 177 | config = LoraConfig( 178 | r=lora_r, 179 | lora_alpha=lora_alpha, 180 | target_modules=["query_key_value", "xxx"], 181 | lora_dropout=lora_dropout, 182 | bias="none", 183 | task_type="CAUSAL_LM", 184 | ) 185 | model = get_peft_model(model, config) 186 | 187 | data = load_from_disk(data_path) 188 | # if data_path.endswith(".json") or data_path.endswith(".jsonl"): 189 | # data = load_dataset("json", data_files=data_path) 190 | # else: 191 | # data = load_dataset(data_path) 192 | 193 | # Check if checkpoints exists 194 | if len(os.listdir(output_dir)) > 0: 195 | last_checkpoint = transformers.trainer_utils.get_last_checkpoint(output_dir) 196 | 197 | # Check the available weights and load them 198 | checkpoint_name = os.path.join(last_checkpoint, "pytorch_model.bin") # Full checkpoint 199 | if not os.path.exists(checkpoint_name): 200 | checkpoint_name = os.path.join( 201 | last_checkpoint, "adapter_model.bin" 202 | ) # only LoRA model - LoRA config above has to fit 203 | 204 | # The two files above have a different name depending on how they were saved, but are actually the same. 205 | if os.path.exists(checkpoint_name): 206 | print(f"Restarting from {checkpoint_name}") 207 | adapters_weights = torch.load(checkpoint_name) 208 | set_peft_model_state_dict(model, adapters_weights) 209 | else: 210 | print(f"Checkpoint {checkpoint_name} not found") 211 | 212 | model.print_trainable_parameters() # Be more transparent about the % of trainable params. 213 | 214 | # if val_set_size > 0: 215 | # train_val = data["train"].train_test_split(test_size=val_set_size, shuffle=True, seed=42) 216 | # train_data = train_val["train"].shuffle().map(generate_and_tokenize_prompt) 217 | # val_data = train_val["test"].shuffle().map(generate_and_tokenize_prompt) 218 | # else: 219 | # train_data = data["train"].shuffle().map(generate_and_tokenize_prompt) 220 | # val_data = None 221 | val_set_size = 0 222 | train_data = data 223 | val_data = None 224 | 225 | # if not ddp and torch.cuda.device_count() > 1: 226 | # # keeps Trainer from trying its own DataParallelism when more than 1 gpu is available 227 | # model.is_parallelizable = True 228 | # model.model_parallel = True 229 | 230 | trainer = transformers.Trainer( 231 | model=model, 232 | train_dataset=train_data, 233 | eval_dataset=val_data, 234 | args=transformers.TrainingArguments( 235 | per_device_train_batch_size=batch_size, 236 | gradient_accumulation_steps=gradient_accumulation_steps, 237 | warmup_steps=100, 238 | num_train_epochs=num_epochs, 239 | learning_rate=learning_rate, 240 | bf16=bf16, # Use BF16 if available 241 | logging_steps=1, 242 | optim="paged_adamw_8bit", 243 | lr_scheduler_type=lr_scheduler_type, 244 | evaluation_strategy="steps" if val_set_size > 0 else "no", 245 | save_strategy="steps", 246 | save_steps=save_steps, 247 | eval_steps=eval_steps if val_set_size > 0 else None, 248 | output_dir=output_dir, 249 | save_total_limit=3, 250 | load_best_model_at_end=True if val_set_size > 0 else False, 251 | ddp_find_unused_parameters=False if ddp else None, 252 | group_by_length=group_by_length, 253 | report_to="wandb" if use_wandb else "none", 254 | run_name=wandb_run_name if use_wandb else None, 255 | ), 256 | data_collator=transformers.DataCollatorForSeq2Seq( 257 | tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True 258 | ), 259 | ) 260 | model.config.use_cache = False 261 | 262 | ## PEFT 구 버전에서 443byte로 저장되는 문제가 있음. 최신 버전에서는 해결되었지만, 일부 훈련 코드에서 아래 코드가 들어간 경우 여전히 443bytes로 저장되므로 주의 필요 263 | ## Reference: https://github.com/huggingface/peft/issues/286 264 | # old_state_dict = model.state_dict 265 | # model.state_dict = (lambda self, *_, **__: get_peft_model_state_dict(self, old_state_dict())).__get__( 266 | # model, type(model) 267 | # ) 268 | 269 | if torch.__version__ >= "2" and sys.platform != "win32": 270 | model = torch.compile(model) 271 | 272 | trainer.train() 273 | 274 | # Save Model 275 | if int(os.environ.get("LOCAL_RANK", 0)) == 0: 276 | if save_merged_model: 277 | print(f'Save LoRA model: {output_dir}') 278 | trainer.model.save_pretrained(output_dir) 279 | 280 | # Saving merged model 281 | print(f'Save merged model: {save_path}') 282 | from peft import AutoPeftModelForCausalLM 283 | os.makedirs(save_path, exist_ok=True) 284 | model = AutoPeftModelForCausalLM.from_pretrained(output_dir, low_cpu_mem_usage=True, torch_dtype=torch.bfloat16) 285 | merged_model = model.merge_and_unload() 286 | merged_model.save_pretrained(save_path, safe_serialization=True) 287 | else: 288 | print(f'Save LoRA model: {save_path}') 289 | trainer.model.save_pretrained(save_path) 290 | 291 | tokenizer.save_pretrained(save_path) 292 | 293 | # clear memory 294 | del model 295 | del trainer 296 | 297 | if __name__ == "__main__": 298 | args, _ = parse_args() 299 | print(args) 300 | train(args) -------------------------------------------------------------------------------- /RAG-SageMaker/dataset/fsi_smart_faq_ko.csv: -------------------------------------------------------------------------------- 1 | no,Category,Information,type,Source 2 | 89,타기관OTP 이용등록방법 알려주세요,"타기관에서 발급받으신 OTP가 통합OTP카드인 경우 당행에 등록하여 이용가능합니다. 3 | [경로] 4 | - 인터넷뱅킹 로그인→ 사용자관리→인터넷뱅킹관리→OTP이용등록 5 | - 신한 쏠(SOL) 로그인→ 전체메뉴→설정/인증→ 이용중인 보안매체선택→ OTP이용등록 6 | 7 | ※ OTP이용등록후 재로그인을 하셔야 새로 등록된 보안매체가 적용됩니다. 8 | 9 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ",인터넷뱅킹,신한은행 10 | 88,공동인증서와 금융인증서 차이점이 무엇인가요?,"공동인증서 (구 공인인증서)는 용도에 따라 은행/신용카드/보험용 무료 인증서와 전자거래범용(수수료 4,400원) 인증서가 있으며 유효기간은 1년입니다. 11 | 공동인증서는 하드디스크나 이동식디스크, 휴대폰 등 원하시는 기기에 저장해서 이용할 수 있습니다. 12 | 인증서를 저장한 매체에서는 인증서 비밀번호로 편리하게 이용할 수 있으나 다른 기기에서 이용하려면 기기마다 복사하거나 이동식디스크에 저장해서 휴대해야 하는 불편함이 있을 수 있습니다. 13 | 14 | 금융인증서는 금융인증서는 금융결제원의 클라우드에 저장하여 이용하는 인증서로 발급/이용 시에 클라우드에 접속이 필요합니다. 15 | 금융결제원 클라우드에 연결만 가능하다면 어디서든 편리하게 이용 가능하지만, PC나 USB, 휴대폰 등 다른 기기로 복사는 불가합니다.(유효기간 3년/발급 수수료 무료) 16 | ※ 클라우드 계정 연결을 위해 휴대폰을 통한 ARS, SMS, 마이인포앱 인증 절차가 필요합니다.",인증서,신한은행 17 | 87,금융인증서 해외에서 발급할 수 있나요?,"해외에서도 인증서 발급 업무는 가능합니다. 다만, 금융인증서 저장을 위한 금융결제원 클라우드 계정 생성 및 연결이 필요한 업무로 해외연락처를 통한 ARS인증이 진행됩니다.",금융인증서,신한은행 18 | 86,클라우드에 보관 중인 인증서는 얼마나 유지 되나요?,"정상적으로 이용하실 경우 금융인증서의 유효기간은 3년이며, 1년 동안 클라우드 이용 이력이 없는 경우는 안전한 이용을 위하여 클라우드 계정 및 저장된 모든 인증서가 삭제됩니다.",금융인증서,신한은행 19 | 85,금융인증서 발급대상은 누구인가요?,"금융인증서는 은행 등의 금융회사에서 실명 확인된 고객을 대상으로 안전하게 발급되며 신한은행 온라인서비스 가입 고객이면 발급가능합니다. 다만 금융인증서는 PC하드디스크나 휴대폰 등에 파일형태로 저장하는 방식이 아닌 금융결제원의 클라우드에 저장되기 때문에 금융결제원의 클라우드 연결을 위해 문자, ARS, 마이인포 앱을 통한 인증 절차가 진행됩니다. ",금융인증서,신한은행 20 | 84,기존 공동인증서를 보유한 상태에서 금융인증서 발급이 가능한가요?,공동인증서와 금융인증서는 별개의 인증서로 두 가지 인증서를 모두 사용할 수 있습니다. ,금융인증서,신한은행 21 | 83,뱅크아이디란 무엇인가요?,"뱅크아이디란, 블록체인기반의 은행권 공동인증서비스로 블록체인 원장을 분산하여 저장/관리함으로써 정보의 신뢰성과 안정성을 높이는 인증기술방식입니다. 기존의 뱅크사인(BankSign)이 뱅크아이디(마이인포) 로 전환되었으며 별도의 앱 설치 없이 신한 쏠(SOL)에서 직접 신청 및 관리가 가능합니다. ",뱅크아이디,신한은행 22 | 82,휴대폰에서도 간편조회서비스 이용 가능한가요?,"ID/PW 방식의 간편조회서비스 이용은 불가합니다. 다만, 이용하시는 휴대폰의 모바일웹 브라우저를 통해 신한은행에 접속 시 회원가입 없이 휴대폰 본인인증과 본인계좌인증을 통해 계좌조회는 가능합니다. 23 | ※ 모바일에서 홈페이지와 동일한 간편조회서비스 이용을 원하실 경우 신한은행 모바일웹 페이지에서 PC버전으로 접속하여 이용해 주시면 됩니다.",간편서비스,신한은행 24 | 81,인터넷뱅킹에서 모바일OTP 인증요청 했는데 푸시메세지가 오지 않아요. 어떻게 해야하나요?,신한 쏠(SOL) 푸시알림 수신에 동의를 하신경우 푸시를 받을수 있습니다. 경로 : 신한 쏠(SOL) 로그인 > 전체메뉴 > 설정/인증  > 일반 > 알림 > 금융정보 알림 항목을 수신동의로 설정 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ,인터넷뱅킹,신한은행 25 | 80,모바일 OTP 사용하고 있는데 인터넷뱅킹에서는 어떻게 이용하는건가요?,"인터넷뱅킹 거래시 보안매체 입력단계에 [인증요청] 버튼 클릭하면 핸드폰으로 푸시메세지가 전달됩니다. 26 | 푸시메세지 터치하여 모바일 OTP 비밀번호 6자리 입력, 인증처리 완료후 인터넷뱅킹에서 이후의 절차를 진행하시면 됩니다. 27 | ※ 스마트폰에 모바일 OTP 푸시(PUSH) 메세지가 오지 않는 경우 메뉴 직접 실행 28 | - 신한 쏠(SOL) [로그인 하지 않음]> 전체메뉴> 설정/인증> 보안매체 전체보기> 모바일OTP> 인증 29 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ",인터넷뱅킹,신한은행 30 | 79,증권사에서 발급받은 인증서는 은행에서 사용 불가한가요? ,"인증서 용도로 구분해 주셔야 합니다. 31 | 증권용(무료) 인증서를 통한 은행 거래는 불가하나, 발급하신 인증서가 전자거래범용(유료) 인증서일 경우 은행에서도 이용 가능합니다.",인증서,신한은행 32 | 78,미래를함께하는따뜻한금융 안전한금융거래를위해준비중입니다.' 화면에서 멈춤 / 로딩중 멈추는데 어떻게 하나요?,"오류 사유는 인터넷 익스플로러 설정값의 영향이거나 이용하시는 장소의 네트워크 영향에 의해 로딩이 원활하지 않은 경우입니다. 33 | 조치방법은 34 | 1. 임시 인터넷 파일 및 쿠키 삭제, 신뢰할 수 있는 사이트 추가 35 | 인터넷 익스플로러 상단의 [도구] > [인터넷옵션]에서 36 | ① [일반Tab] > 검색기록항목의 [삭제] 버튼 클릭 ""임시 인터넷 파일 및 사이트 파일"" , ""쿠키 및 웹사이트 데이터"" 항목만 체크하고 [삭제] 37 | ② [보안Tab] > 신뢰할 수 있는 사이트 > [사이트] 클릭하여 [ https://*.shinhan.com ] 추가 후 적용 및 확인하십시오. 38 | 또는, 당행 보안프로그램 삭제 열려있는 브라우저 종료 후, 시작 > 제어판의 [프로그램 추가/제거] (총 4개이며 보이지 않으면 제거할 필요없음) 39 | - AhnLab Safe Transaction, 40 | - iniLINE CrossEX Service, 41 | - INISAFE CrossWeb EX v3.0, 42 | - TouchEn nxKey with E2E for 32bit 43 | 3. 새 브라우저 열어서 개인인터넷뱅킹 재접속 후 [통합설치] 진행 하세요. 44 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ",,신한은행 45 | 77,해외에서 예적금해지중 ARS 추가인증을 할 수 없어 해지를 할수 없습니다. 어떻게 해야하나요?,"해외IP로 인터넷뱅킹접속시 시간에 따라 추가인증 방법이 상이합니다. (한국시간 기준) 46 | 은행영업일 9시~18시 : 해외거주/체류자인증으로 추가인증 가능하며, 통신국가코드, 연락가능한 해외 전화번호 입력후 본인확인신청 요청하시면 상담사와 연결되어 본인확인후 인증처리 가능합니다. 47 | 은행영업일 9시~18시 이외 : 당행에 등록된 연락처로 ARS 추가인증 진행합니다.",인터넷뱅킹,신한은행 48 | 76,해외체류중인 외국인으로 인증서발급시 추가인증을 할수 없는데 어떻게 해야하나요?,"외국인이신 경우 당행 영문뱅킹에서 공동인증서 발급하시면 추가인증단계에서 추가인증 생략 선택 후 발급가능합니다. (단, 국내시간 기준으로 심야시간 0시~ 06시에는 추가인증 생략이 불가합니다.) 49 | [경로] 50 | ① 신한은행 홈페이지 접속 → 우측상단 GLOBAL→ English → PERSONAL BANKING 51 | ② Digital Certificate Center → Issue/reissue a digital certificate ",인터넷뱅킹,신한은행 52 | 75,해외IP 차단서비스 가입되어있다고 이체를 할수 없다고 나옵니다. 어떻게 해야 하나요?,"해외IP 차단서비스에 가입되어 있는경우, 해외IP로 개인뱅킹 접속하여 보안매체 인증해야 하는 거래는 하실수 없도록 제한됩니다. 53 | 해외IP 차단서비스 해제는 본인이 신분증 지참후 영업점 방문하셔야 해제 가능합니다. 54 | 단. 해외 체류중인 경우 출입국사실증명서를 보내주시면 고객센터에서도 해제가능하니 55 | 신한은행 고객상담센터 ☎ 82-2-3449-8000번으로 문의주시면 자세히 상담해드리겠습니다. ",인터넷뱅킹,신한은행 56 | 74,영문뱅킹에서도 간편조회서비스 이용가능한가요? ,영문뱅킹에서는 간편조회서비스 이용불가합니다. ,간편서비스,신한은행 57 | 73,아이핀으로 홈페이지회원 가입한 고객은 간편조회서비스 이용할 수 없나요?,"아이핀으로 홈페이지 회원가입하신 고객은 홈페이지에서 개인회원으로 전환 후 간편조회서비스 이용가능 합니다. 58 | 홈페이지 이용자아이디와 비밀번호 로그인→ (우측상단) 고객센터→ 회원서비스 →[ I-Pin → 개인회원 전환]→ '보기' 클릭 후 약관동의→ 개인회원정보 등록→ 확인 59 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ",간편서비스,신한은행 60 | 72,간편조회서비스는 회원가입해야만 이용할 수 있나요?,"간편조회서비스에는 로그인을 위해 회원가입이 필요한 서비스와 회원가입 없이 누구나 이용 가능한 서비스가 있습니다. 61 | 회원가입 및 로그인이 필요한 메뉴는 62 | ① 계좌조회, ② 시효포기채권감면조회, ③ 대학등록금 납부하기(당행인터넷뱅킹가입자만 이용가능, 인증서 로그인필요), ④ 제로페이Biz 승인내역 조회 입니다. 63 | 64 | 회원가입 없이 이용 가능한 메뉴는 65 | ① 예금잔액증명서조회, ② 자기앞수표조회, ③ 환율조회,환가료율조회, ④ 여신증명서 발급사실 확인 , ⑤ 지급보증서조회, ⑥ 대학등록금 (납부조회. 영수증DM신청,고지서출력 ), ⑦ 법원업무 (예약접수신청 , 예약접수조회 , 계좌환급조회 ,송달료조회), ⑧ 상조예치금 조회  입니다. 66 | ",간편서비스,신한은행 67 | 71,홈페이지 이용자아이디 여러 개 사용할 수 있나요?,"홈페이지 이용자 아이디는 개인의 경우 1인 1개만 이용 가능하고 기업의 경우에는 1개의 사업자번호당 사용자별로 이용자아이디를 복수로 사용할 수 있습니다. 68 | ※ 개인사업자의 경우 개인과 기업 각각 이용자아이디를 발급하여 복수로 이용 가능합니다. 69 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다. ",홈페이지,신한은행 70 | 70,홈페이지상에 제가 등록한 칭찬/불만/제안사항 조회할 수 있나요?,"로그인 후 등록한 접수내용에 대해서 확인 가능합니다. 71 | [경로 안내] 홈페이지 로그인 → (우측상단) 고객센터 → 금융소비자보호 → [칭찬/불만/제안] 나의 접수현황/취소신청 72 | ※ 로그인 하지 않은 상태에서도 홈페이지에 칭찬/불만/제안 접수 가능하나 73 | 추후, 본인 접수내용을 확인하려면 로그인해서 등록한 내용에 대해서만 [나의 접수현황]에서 조회 가능합니다. 74 |  기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",홈페이지,신한은행 75 | 69,영업점에서 인터넷뱅킹 가입하고 왔는데 홈페이지회원은 별도로 가입해야 하나요?,"인터넷뱅킹 가입과 홈페이지 회원은 개별로 운영되고 있습니다. 76 | 인터넷뱅킹만 가입을 하셨다면 별도로 홈페이지에 회원가입 후 홈페이지 로그인이 가능합니다. 77 | ※ 영업점에서 인터넷뱅킹 가입 시 전계좌조회서비스 가입하신 고객은 홈페이지 상에서 이용자비밀번호 등록 후 이용가능합니다. 78 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",홈페이지,신한은행 79 | 68,영업점에서 공동인증서 발급 가능한가요?,"본인이 방문하여 요청 시 공동인증서 창구발급서비스 이용이 가능합니다. \r\n단, 창구에서 발급신청을 하시더라도 인증서 저장은 고객님의 스마트폰에서 직접 진행해주셔야 합니다. \r\n\r\n신한 쏠(SOL) 접속 설정/인증 공동인증서 인증서창구발급 \r\n주민등록번호, 영업점에서 받은 6자리 인증번호 입력(30분 이내) 공동인증서 \r\n암호 입력(2회) 확인[유의사항]\r\n\r\n① 본인만 신청 가능하며 대리인 신청 불가② 타행에 은행/신용카드/보험용 인증서가 발급되어 있는 경우 신청 \r\n 불가 \r\n\r\n※ 영업점 창구에서 공동인증서 창구 발급 신청 시, 이전에 발급된 공동인증서는 자동 폐기 됩니다. ",,신한은행 80 | 67,영업점에 개명신청을 했는데 인증서는 예전이름이 그대로 보여요 인증서명 변경은 어떻게 하나요?,"인증서 정보가 발급받은 저장매체(하드디스크, USB 등)에 파일형태로 저장되므로 은행을 방문하여 개명신청을 하셨더라도 PC에 저장된 인증서 파일의 정보는 자동으로 반영되지 않습니다. 인증서 만료 시까지는 기존 인증서를 그대로 사용하셔도 되고 개명된 이름으로 인증서명 변경을 원하실 경우 인증서를 재발급 받으면 변경된 이름으로 적용이 됩니다.",인증서,신한은행 81 | 66,신한쏠로 인증서복사 어떻게 하나요?,"신한 쏠(SOL)에서 공동인증서를 이용하기 위해서는 인증서가 저장된 PC 또는 이동식디스크(USB)에 저장된 인증서를 스마트폰으로 복사한 후 이용 가능합니다. 인증서 복사 시에는 반드시 인증서가 저장되어 있는 PC에서 진행하여야 하며, 사용할 스마트폰이 있어야 합니다. ■ 인증서 PC → 스마트폰 복사방법1. 신한은행 개인뱅킹 접속① 인증센터 > 공동인증서(구 공인인증서)② (화면 상단)스마트폰 인증서복사>PC → 스마트폰(어플)복사③ PC→스마트폰인증서복사 클릭④ (복사할 인증서 선택)인증서 암호입력>확인⑤ 인증번호 입력칸 확인2. 신한 쏠(SOL) 접속① 초화면 우측 하단 메뉴>설정/인증>공동인증서② 인증서복사> PC → 휴대폰복사 ③ 인증번호 8자리 생성>생성된 번호 PC에 입력>확인④ 신한 쏠(SOL) [인증서 복사가 완료되었습니다] 확인※발급된 인증서가 없는 경우 인터넷뱅킹 또는 신한 쏠(SOL)에서 은행/신용카드/보험용 인증서 발급이 가능합니다.",,신한은행 82 | 65,인터넷뱅킹 보안프로그램 - 해킹방지 솔루션 ASTx 기능 안내 해줘요,"인터넷뱅킹 이용시 설치되는 해킹방지 솔루션(ASTx : Ahnlab Safe Transaction)은 다음과 같은 기능으로 동작합니다. 83 | ① 기본 보호모드 : PC부팅시 실행host 파일 보호피싱/파밍 사이트 경고 및 차단자동업데이트 등 84 | ② 고급 보호모드 : 신한은행 홈페이지/인터넷뱅킹 접속시 실행악성코드 탐지 및 수동검사 치료웹브라우저 메모리 데이터 보호 개인 방화벽화면캡쳐방지 및 원격제어 차단단말정보 수집 등고객님의 안전한 인터넷뱅킹 이용을 위하여 현재 저희 신한은행에서 85 | ASTx프로그램을 설치하면 PC부팅시 신한은행 인터넷뱅킹 접속 여부와 상관없이 기본 보호모드가 동작하게 되며, 저희 신한은행 인터넷뱅킹 접속시 고급 보호모드가 동작하게 됩니다. 86 | * PC부팅시 동작하는 기본 보호모드를 제외하고 인터넷뱅킹을 이용하시려면 아래 해킹방지 솔루션 ASTx (기본 보호모드 제외 버전)을 설치후 이용 가능합니다.",,신한은행 87 | 64,"보안프로그램 ASTx, CrossWebEx, TouchEnNxKey 이 설치되어 있어도 설치 팝업이 반복 출력되는 경우","보안프로그램 ASTx, CrossWebEx, TouchEnNxKey 이 설치되어 있어도 설치 팝업이 반복 출력되는 경우",,신한은행 88 | 63,Internet Explorer 브라우저 사용 시 브라우저 화면이 흰색으로 변경되는 현상,"2017년 5월에 개편된 인터넷뱅킹 및 홈페이지는 웹표준 기술(HTML5)로 구축되어 있습니다. 그러나 인터넷 익스플로러 브라우저는 과거 비표준 기술인 ActiveX를 사용하기 위하여 브라우저 옵션 및 설정이 변경되는 경우가 많기 때문에 개편된 사이트의 HTML5 기술이 동작하지 않아 화면이 하얗게 보이는 현상입니다. HTML5를 지원하지 않는 IE8, IE9 브라우저는 상위 IE 혹은 크롬을 설치하여 이용해 주시기 바랍니다. 89 | 1. 신뢰할 수 있는 사이트 추가, IE 브라우저의 도구 인터넷 옵션을 선택 후① '보안' 탭 선택② 상단의 ‘보안 설정을 보거나 변경할 영역을 선택하십시오.’ 에서 “신뢰할 수 있는 사이트” 클릭③ ‘사이트’ 버튼 클릭④ 영역에 웹 사이트 추가에 ‘*.shinhan.com’ 입력 후 ‘추가’ 버튼 클릭⑤ ‘이 영역에 있는 모든 사이트데 대해 서버 검증(https:) 필요’ 해제2. 추가 설정 IE 브라우저의 도구 인터넷 옵션을 선택 후 ① '보안' 탭 선택 후 ‘이 영역에 적용할 보안 수준’의 “기본 수준” 버튼 클릭 ② ‘고급’ 탭 선택 후 ‘설정’ 부분의 보안 영역의 ‘DOM 저장소 사용’ 체크③ 인터넷 옵션 창의 하단 ‘적용’ 버튼 클릭",,신한은행 90 | 62,구글 Chrome 브라우저 안내,"고객님, 컴퓨터 사용 환경이 윈도우 XP/익스플로러 7.0 혹은 8.0을 사용중이시면 좀 더 빠르고 편리하게 인터넷뱅킹 사용을 위해 구글 Chrome브라우저를 이용해 보시기 바랍니다. 91 | 설치 링크는 https://www.google.com/intl/ko/chrome/?hl=ko&brand=CHMA&utm_campaign=ko&utm_source=ko-ha-apac-ko-bk&utm_medium=ha 입니다.",,신한은행 92 | 61,해외고객 추가인증 방법,"먼저 이용에 불편을 드려 대단히 죄송합니다. 2013년 9월 전자금융사기 예방서비스 전면 시행으로 인해, 국내외에서 뱅킹이용시 거래에 따라 추가인증을 거치게 되었습니다. 93 | ① 인증서 발급/재발급, 1일 누적 300만원 이상 이체시 시행하는 추가인증 방법(누적금액은 변경될수 있습니다.)해외IP로 접속하신 부분이 자동으로 확인되면 ‘해외체류 확인’ 항목이 활성화 되기 때문에 법무부 출입국관리사무소에 출국정보를 확인 받는 부분에 동의하시면 ARS 인증없이 거래가 가능합니다. 94 | ※ 해외 소재한 국내기업의 내부 인터넷망을 이용하시는 경우 국내IP로 인식하여 해당 추가 인증기능 활성화가 안되는 경우가 있으니 유의부탁드립니다. 95 | ② 해외에서 온라인을 통한 예금해지/대출실행 업무시 추가인증 방법한국시간 기준으로 은행 영업일의 오전9시부터 오후6시 사이에 뱅킹에 접속하시면 해외 현지 연락처를 직접 입력하여 추가인증의 진행이 가능하도록 되어있습니다. 96 | ③ 고객정보 보호 목적으로 인터넷뱅킹상에서 개인정보 변경 등의 일부 중요업무이 경우는 ARS인증만을 시행하도록 하고 있습니다. 왜냐하면, 타인에 의한 정보변경 시도 등을 차단하고자 하는 부분이므로 만약 해외에서 070 등 인터넷전화를 사용하시는 경우 미리 한국에서 정보변경을 하지 못하셨다면 고객센터 82-2-3449-8000 또는 82-2-3708-8000으로 상담사에게 문의하시기 바랍니다. 97 | ④ “불법거래 방지를 위해 이체거래가 정지되었다”라고 뜨는 경우 98 | 82-2-3449-8000 또는 82-2-3708-8000으로 연락주셔서 상담직원을 통한 사고등록 해제가 가능합니다. 이체제한 해제는 24시간 가능하며, 본인확인을 위해 몇가지 정보확인을 거치는 점 양해바랍니다. 99 | 82-2-3449-8000 번호로 전화주셔서 음성이 나오면 차례대로 6-3-3-0 코드를 따라서 24시간 사고관련 상담직원과 통화가 가능합니다 100 | 해외에 계신 고객분들도 뱅킹을 이용하실 수 있도록 편의성 부분도 많이 고려하고 있으나, 최근 해외IP를 통해 전자금융서비스 이용고객분들께 피해를 입히려는 시도가 다수 발생하고 있기 때문에 제한적으로 해외체류 확인 항목을 적용할 수 밖에 없어서 안타깝게 생각합니다. 편리성 못지 않게 고객분들의 소중한 자산을 지키는 것이 중요하다보니 이와 같이 시행하고 있는 점 다시 한번 양해 부탁드립니다.",인터넷뱅킹,신한은행 101 | 60,홈페이지에서 계좌거래내역 조회할 수 있나요?,"홈페이지회원 가입 후 간편조회서비스를 통해 계좌 거래내역 조회 가능합니다. 102 | 홈페이지 회원가입 후 [간편계좌관리]에서 이용하실 계좌를 등록하면 계좌 거래내역 조회 가능합니다. 103 | 인터넷뱅킹 미가입자도 이용가능합니다.",간편서비스,신한은행 104 | 59,이체한도 증액은 어떻게 하나요?,"영업점 방문하지 않아도 모바일뱅킹 신한 쏠(SOL) 에서 비대면실명 인증후 통합이체한도 증액 가능합니다. 통합이체한도는 이용하시는 보안매체에 따라, 보안등급별 최고 한도내에서 증액가능합니다. 105 | OTP카드 창구 발급 시 5,000원(카드형 10,000원)의 수수료가 부과됩니다. 당행에서만 사용가능한 모바일OTP는 영업점 방문없이 신한 쏠(SOL) 에서 무료로 발급가능합니다. 106 | ※ 모바일OTP를 이용하시는 경우 쏠(SOL)에서 이체한도 증액은 1회 1천만원 이하, 1일 5천만원 이하로 증액 가능합니다. 107 | ※ 만 14세 미만의 경우 법정대리인(부모/친권자/후견인)에 의해 영업점 방문하여 증액 가능하며 1일, 1회 1백만원 이하로 이체한도 제한됩니다. 108 | 통합한도란 인터넷뱅킹/신한 쏠(SOL)/폰뱅킹 등을 사용하는 경우 1일 또는 1회에 이체 가능한 통합 이체한도를 말합니다. 109 | * 인터넷뱅킹/신한 쏠(SOL)/폰뱅킹에서 거래한 이체금액의 합이 통합이체한도의 범위를 초과할 수 없습니다. 110 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",인터넷뱅킹,신한은행 111 | 58,MS 인터넷 익스플로러 환경에서의 피해방지를 위한 안내 해줘.,"마이크로소프트의 인터넷 익스플로러 브라우저에서 신규 취약점이 발견됨에 따라 신한온라인서비스 이용고객의 피해를 예방하기 위해 Windows 자동 업데이트 사용을 권장합니다. 112 | Windows의 자동 업데이트 기능에 대한 설명 및 설정은 Microsoft 고객지원 페이지를 참조하시기 바랍니다.",,신한은행 113 | 57,대출 사칭 문자 받앗어요,최근에 신한은행 사칭 하여 마이너스 통장 발급대상이라는 문자를 보내고 있는거 같습니다. 당행에서는 마이너스 통장 발급 대상 문자를 보내드리지는 않습니다. 대출 가능 여부는 개인 신용정보 조회 동의 후 심사를 통해서만 확인이 가능하기 때문에문자 발송을 하지 않습니다.참고로 고객님 정보가 노출이 되었기 때문에 아래 내용을 참고하여 주시기 바랍니다. ,,신한은행 114 | 56,인쇄 기능 장애 발생시 확인 방법,기존 설치된 인쇄관련 프로그램이 손상되었거나 정상 구동되지 않아 발생할 수 있습니다. 먼저 설치된 프로그램을 삭제하고 재설치하는 방법 안내드리겠습니다 원활한 진행을 위해서 열려있는 브라우저나 프로그램은 모두 종료 후 진행해 주시기 바랍니다. [삭제방법] 제어판 → 프로그램 추가/제거(또는 프로그램 및 기능) → Printmade3 삭제 [설치방법] 아래 인터넷뱅킹 통합프로그램설치 버튼을 클릭 → 설치화면 하단의 인터넷화면 인쇄 프로그램(Printmade)을 재설치 하시기 바랍니다. ,,신한은행 115 | 55,예약이체를 했는데 이체일에 송금이 안된 경우는?,"모든 예약이체 등록건은 예약이체 지정일 지정된 시간(오전 7시, 오전 9시, 오전 11시, 오후 1시, 오후 3시, 오후 5시, 오후 7시, 오후 9시) 이전에 출금할 계좌에 송금액이 입금이 되어야 이체가 실행될 수 있습니다. 116 | 이체가 되지 않았다고 판단되실 경우에는 반드시 먼저 ""예약이체처리결과조회"" 또는 ""계좌 거래내역""을 확인 하시어 이체여부 및 출금여부를 확인하시기 바랍니다. 또한 예약이체 신청시 SMS 결과통보신청을 하셨다면 요청하신 휴대폰번호로 수신된 문자를 확인해주시기 바라며, 정해진 시간에 이체되지 않은 경우 신한은행 고객상담센터 ☎ 1599-8000번으로 문의 주시기 바랍니다.",인터넷뱅킹,신한은행 117 | 54,매월 일정금액을 타은행으로 자동송금 신청시 이체기준 알려줘,"타은행으로 매월 일정금액을 자동송금 신청 하셨을때 당월 또는 익월은 입력하지 말고, 신청등록을 하십시오. 자동이체 등록일을 기준으로 자동 이체일이 등록일보다 빠르면 익월부터 자동으로 이체가 되며, 자동 이체일이 등록일보다 늦으면 당월부터 자동이체가 되기 때문입니다. 예시 자동이체 신청 등록일 15일, 자동 이체일 10일이면 익월 10일부터 이체 자동이체 신청 등록일 15일, 자동 이체일 20일이면 당월 20일부터 이체",,신한은행 118 | 53,이체한도란 무엇인가요?,"이체한도란 인터넷뱅킹, 폰뱅킹, 모바일뱅킹 등을 통하여 이체할 수 있는 거래한도를 말합니다. ① 1일/1회 이체한도 1일 이체한도란 하루에 거래할 수 있는 이체금액의 합을 말하여, 1회 이체한도란 1회에 이체하실 수 있는 금액의 한도를 말합니다. ② 통합이체한도 인터넷뱅킹, 폰뱅킹, 모바일뱅킹 등을 고객님이 사용하시는 경우 고객님이 1일 또는 1회에 이체하실 수 있는 이체한도를 말합니다. 인터넷뱅킹, 폰뱅킹, 모바일뱅킹 등에서 고객님이 거래하신 이체금액의 합이 통합이체한도의 범위를 초과할 수 없습니다. ③ 최고이체한도 금융감독원에서는 개인의 전자금융 시 이용할 수 있는 이체한도의 가이드라인을 정하고 있습니다.(인터넷뱅킹 : 1일 5억원, 1회 1억원 이내)",인터넷뱅킹,신한은행 119 | 52,12개월 장기미이체로 이체서비스가 중지된 경우 어떻게 해제하나요?,"최근 12개월동안 인터넷뱅킹/폰뱅킹/모바일뱅킹에서 이체서비스를 이용하지 않은 경우 고객님의 금융자산 보호를 위하여 이체서비스가 중단됩니다. 다시 이체를 원하시는 경우에는 인터넷뱅킹/폰뱅킹/신한쏠(SOL)에서 이체정지 해제를 하실 수 있습니다.(단, 인터넷뱅킹 출금계좌가 등록되어 있는 경우) [경로] ① 당행 고객정보에 등록되어있는 연락처로 ARS 추가인증 가능한 경우: - 인터넷뱅킹 로그인→ 사용자관리→ 인터넷뱅킹관리→ 장기미사용 정지해제 (또는) - 쏠(SOL) 로그인→ 전체메뉴→ 이체 → 이체관리→ 장기미사용정지 해제 ② ARS 추가인증이 불가한 경우 (해외체류 고객등) : - 인터넷뱅킹에서는 해제 어려우며, 쏠 (SOL)에서 해제 가능합니다. 쏠(SOL)의 경로는 위와 동일하며, 비대면 실명인증후 해제 가능합니다. 비대면실명인증 - 만 14세이상 가능 - 신분증확인 (주민등록증 또는 운전면허증 또는 여권) - 본인확인 (본인명의 계좌인증 또는 영상통화) 진행 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다",인터넷뱅킹,신한은행 120 | 51,인터넷뱅킹 보안프로그램안내해줘,"신한은행에서 고객님의 안전한 금융거래를 위해 제공되는 설치 필수 또는 권장 프로그램은 아래와 같습니다. 통합설치프로그램 : VeraProt 안전한 금융거래를 위해 제공하는 보안 프로그램들에 대해서 쉽고 편리하게 설치할 수 있도록 하는 프로그램입니다. 개인 PC방화벽/백신 보안 : ASTx 악성코드 탐지/차단, 네트워크 차단, 피싱/파밍 방지, 메모리 해킹 방어, 단말정보 수집 등 보안기능을 제공함으로써 인터넷뱅킹 이용시 발생할 수 있는 사용자의 피해를 차단할 수 있는 프로그램입니다. 키보드 보안 : TouchEn-NX Key 사용자가 키보드를 통해서 입력하는 Data를 안전하게 관리하여 기존에 알려져있거나 혹은 알려지지 않은 해킹툴들이 사용자의 키보드 입력정보를 해킹하지 못하도록 방어하는 키보드 보안 서비스입니다. 다운로드받지 않을 경우, 가상키보드가 화면에 표시되며, 설치시 물리적키보드 입력이 가능합니다. 공인인증프로그램 : CrossWebEX 다양한 환경의 PC에 설치되는 사용자모듈과 전자서명 업무 및 제출된 공인인증서의 유효성 검증을 수행하는 프로그램입니다.",,신한은행 121 | 50,"인터넷뱅킹에 로그인한 후 10분이 지나지 않았는데, 다시 로그인하라는 안내는 왜그래?","PC환경에 따라 로그인을 했는데도 계속해서 로그인을 다시 하라는 현상이 발생할 수가 있는데요. 이는 인터넷뱅킹/홈페이지 로그인시 세션정보가 제대로 전달되지 않기 때문에 발생하는 현상입니다. 문제 해결을 위해 다음과 같이 환경설정을 변경하여 주시기를 바랍니다. ① 인터넷 익스플로러 상단메뉴에서 [도구] => [인터넷옵션] => [개인정보탭] 선택 ② 설정값(슬라이드바)을 '낮음'으로 변경. 만약, 슬라이드바가 보이지 않는 경우 '기본값' 버튼을 눌러주시면 슬라이드바가 나타납니다. 혹은 개인적으로 이용하시는 방화벽이나 백신프로그램이 있으면 잠시 실행을 멈추신 후 이용하여 주시기를 부탁드립니다. 보다 상세한 내용은 저희 신한은행 콜센터의 인터넷뱅킹 전문상담원분께 문의주시기를 부탁드립니다.(1577-8000, 코드번호 4-4-1번)",,신한은행 122 | 49,전산통합이후 신계좌번호로 바꿔야만 하나요? 신계좌로 바꾸면 과거 자동이체 건은 어떻게 되나요?,"통합이후 고객수가 크게 늘어 기존 11자리 체계에서 12자리 체계로의 변경이 필요했습니다만, 신계좌로 전환하여도 기존 구계좌번호를 이용하시는데 (기존 자동이체 및 급여입금 등) 전혀 문제가 없슴을 말씀드립니다.",,신한은행 123 | 48,홈페이지 탈회하고 싶습니다. 어떻게 하나요?,"홈페이지 로그인 후 회원탈퇴를 하실 수 있습니다. 홈페이지 상단에 있는 고객센터 > 회원서비스 > 회원탈퇴 메뉴에서 회원탈퇴 진행할 수 있습니다. 혹시, 비밀번호를 분실하셨다면 ID/PW 로그인 화면 하단 비밀번호 재설정 메뉴를 통해서 비밀번호 재설정하고 로그인 후 회원탈퇴 가능합니다. 기타 문의는 신한은행고객센터 (☎ 1599-8000번)으로 문의 바랍니다.",홈페이지,신한은행 124 | 47,홈페이지 회원아이디를 변경하고 싶습니다.,"회원탈퇴를 하시고, 사용하시고 싶은 아이디로 신규 회원가입신청을 하시면 됩니다. 다만, 사용하시고 싶은 아이디가 이미 다른 고객이 사용하고 있는 경우에는 사용하실 수 없습니다. 기타 문의는 콜센터 1599-8000번으로 문의 바랍니다.",홈페이지,신한은행 125 | 46,홈페이지 고객정보를 수정하고 싶습니다.,"홈페이지 회원로그인 후 정보수정을 하실 수 있습니다. 홈페이지 상단에 있는 [고객센터]를 클릭하여, 회원서비스 → 회원정보변경 메뉴에서 회원정보변경을 이용하시기 바랍니다. 인터넷뱅킹의 회원정보는 인터넷뱅킹으로 로그인 하신 후 사용자 관리메뉴를 이용하여 수정하시기 바랍니다. 기타 문의는 콜센터 1599-8000번으로 문의 바랍니다.",홈페이지,신한은행 126 | 45,아이디/비밀번호가 생각나지 않습니다. 어떻게 해야 하나요?,"홈페이지 상단에 있는 [고객센터] > 회원서비스 메뉴에서 아이디 찾기, 비밀번호 재설정하여 이용하시기 바랍니다. 신한은행 개인 인터넷뱅킹(bank.shinhan.com) 접속 후 1. 아이디 찾기 ① 고객센터 > 회원서비스 > 아이디찾기 또는 뱅킹로그인 > 아이디찾기 ② [보기] 클릭 > 약관확인 > 동의함 체크 ③ 성명/생년월일/계좌번호/계좌비밀번호 입력 > 확인 ④ 이용자아이디 확인 완료 2. 비밀번호 재설정 ① 고객센터 > 회원서비스 ② 회원정보변경>비밀번호 찾기(재설정) ③ (개인회원)약관보기 및 동의 ④ 본인확인(이름, 생년월일, 아이디, 계좌번호, 계좌비밀번호 입력) ⑤ 사용할 비밀번호/비밀번호 확인 2회 입력>확인 ※ 영문, 숫자, 특수문자 포함 8-15자리 이내 ⑥ 비밀번호 재설정 완료 진행과정 중 궁금하신 내용은 고객센터(1599-8000)으로 문의 부탁드립니다.",,신한은행 127 | 44,만14세 미만의 고객은 홈페이지 회원가입이 가능하나요?,"만 14세 미만 고객은 회원가입시 '정보통신망 이용촉진 및 정보 등에 관한 법률' 및 '개인정보보호지침'에 따라 법정대리인의 정보활용 동의가 필요합니다. 당행 영업점을 통한 보호자분의 홈페이지 회원 법정대리인 등록이 완료된 고객님에 한하여 회원등록이 가능하므로 홈페이지 회원 법정대리인 등록이 안된 고객님의 보호자분께서는 먼저 가까운 영업점을 방문하여 주시기 바랍니다. [구비서류] - 법정대리인 실명확인증표 - 미성년자 기준으로 발급된 특정 또는 상세 기본증명서 - 미성년자 기준으로 발급된 가족관계증명서 * 은행 방문일로부터 3개월 이내 발급한 서류로 준비해주셔야 하며, 성함과 주민등록번호 13자리가 모두 기재되어 있어야합니다. 기타 문의는 콜센터 1599-8000번으로 문의 바랍니다.",홈페이지,신한은행 128 | 43,홈페이지 회원가입은 어떻게 해야 하나요?,"홈페이지 상단에 있는 [고객센터]를 클릭하여, 회원서비스 → 회원가입메뉴에서 회원가입 메뉴를 이용하시기 바랍니다. 기타 문의는 콜센터 1599-8000번으로 문의 바랍니다.",홈페이지,신한은행 129 | 42,2차 증빙서류는 무엇입니까?,"재직증명서, 의료보험증, 전기,전화,수도,가스 요금 청구서나 영수증 등 본인만이 소지할 수 있는 성격의 자료입니다. 고객님께서 영업점에 제시한 신분증이 마모 등으로 인해 확인이 불확실할 경우 2차 증빙서류를 함께 제시할 것을 요청할 수 있습니다.",인터넷뱅킹,신한은행 130 | 41,온라인 서비스 신청시 구비서류는?,"온라인 서비스 신규는 본인만 가능하고 가입시 필요서류는 다음과 같습니다. 온라인 서비스 신규 시 필요서류 : 본인 신분증. 신분증 : 국가기관 또는 지방자치단체, 교육법에 의한 학교의 장이 발급한 것으로서 성명, 주민등록번호가 기재되어 있고 부착된 사진에 의하여 본인임을 확인할 수 있는 증표 (주민등록증, 운전면허증, 공무증, 여권, 노인복지카드(경로우대증), 장애인복지카드(장애인등록증 포함), 국가 유공자증, 학생증, 외국인등록증, 국내거소신고증 등). 만14세 미만 미성년자. 만 14세 미만 미성년자는 법정대리인에 의한 신청한 가능합니다. 1. 부모가 혼인중인 경우 부 또는 모 신분증, 미성년자 기준으로 발급된 특정기본증명서, 미성년자 기준으로 발급된 상세가족관계증명서. 2. 친권자나 후견인이 지정된 경우 친권자 또는 후견인의 신분증, 미성년자 기준으로 발급된 특정 기본 증명서, 미성년 자녀 기준으로 발급된 일반 가족관계증명서(후견인은 생략). 발급서류는 3개월이내 발급된 원본으로 주민등록번호 13자리가 모두 표기된 서류로 준비부탁드립니다.",인터넷뱅킹,신한은행 131 | 40,회원탈퇴 후 메일이 계속와요.,"인터넷뱅킹가입을 하시면 예금/대출/카드 등 거래에 대한 안내(예:예금만기 등)외에 영업점안내메일 등 몇가지 부가서비스가 기본제공됩니다. 부가서비스는 홈페이지에 로그인하셔서(인터넷뱅킹사용자는 별도 회원가입이 필요없습니다.) 이메일서비스의 수신/거부 등 변경을 하시면 됩니다. 다만, 인터넷뱅킹을 해지하시더라도 예금,카드,대출 등의 거래가 남아있을 수 있기에 메일서비스는 계속 제공됩니다. 따라서 고객님의 경우에는 기존에 제공되는 메일서비스가 계속 남아있어 부가서비스 메일을 받으신 것이며. 정보가 유출되거나 하는 경우는 절대 없으니 안심하시기 바랍니다. 더이상 부가서비스 이메일 수신을 원하지 않는 경우에는 홈페이지의 회원가입을 하신 후 회원서비스의 이메일서비스에 가셔서 변경하시면 됩니다.",,신한은행 132 | 39,pdf 파일을 보려면 어떻게 해야 하나요?,"확장자가 .pdf 인 파일을 열어 보기 위해서는 Acrobat Reader가 있어야 합니다. Acrobat Reader는 인터넷에서 무료로 다운로드 받으실 수 있습니다. 다운로드 받으시려면 아래 주소를 클릭하시기 바랍니다. 133 | http://www.adobe.com/kr/products/acrobat/readstep2.html",인터넷뱅킹,신한은행 134 | 38,이메일주소변경 안내,이메일주소 변경방법은 아래와 같습니다. ① 인터넷뱅킹 로그인 ② 사용자관리 고객정보 고객정보조회/변경메뉴 클릭 ③ 고객님의 다른 정보와 함께 이메일정보 변경 가능,인터넷뱅킹,신한은행 135 | 37,보내신 메일의 이름은 제이름이 아닙니다.라고 나옵니다.,"""신한은행에서 발송하는 메일 중 다른 고객이름으로 발송되는 경우가 가끔씩 발생합니다. ① 이메일계정 제공하는 포탈업체에서 동일한 이메일주소를 타 고객이 사용하시다가 본인이 해지하는 경우 ② 일정기간 이메일이 사용되지 않으면 해당 이메일계정 제공업체에서 임의로 삭제하여 이메일주소를 타인이 사용할 수 있도록 하는 경우 ③ 저희 은행을 이용하시는 고객이 이메일주소를 잘못 등록하시는 경우에 발생합니다. ※ 고객센터(1577-8000)로 전화주시면 해당이메일주소를 등록하신 고객님께 연락하여 향후 재발송되지 않도록 조치하겠습니다.",인터넷뱅킹,신한은행 136 | 36,이메일서비스 신청 및 해지 방법은?,이메일서비스는 인터넷뱅킹의 서비스메뉴 중 뱅킹보안서비스 > 입출내역통지서비스 > 입출내역 E-Mail통지서비스에서 신청 및 해지하실 수 있습니다.,인터넷뱅킹,신한은행 137 | 35,로그인은 어디서 하나요?,"인터넷뱅킹에 로그인하시려면 신한은행 홈페이지에서 개인 → 뱅킹로그인 버튼을 클릭하시면 됩니다. 참고로 인터넷뱅킹에 로그인하시려면 우선 공동인증서 또는 금융인증서를 발급받으셔야 하며, '인증센터'에서 발급받으실 수 있습니다. 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",인터넷뱅킹,신한은행 138 | 34,대학 등록금을 인터넷뱅킹으로 납부할 수 있습니까?,"당행과 수납체결이 되어있는 대학교의 등록금은 개인인터넷뱅킹을 통해 납부가 가능합니다. 공과금/법원 기타 대학등록금 납부 메뉴에서 납부가 가능하며, 해당메뉴로 접속후 학교와 학번을 입력후 납부 하시면 됩니다. 해당메뉴로 등록금을 납부할 경우 온라인 이체한도 미포함 거래입니다. 또한 등록금 납부 가상계좌를 알고 계실경우 이체 메뉴를 이용하여 즉시이체하셔도 됩니다. 이럴경우 이체한도에 포함되는 거래입니다. 가상계좌로 이체하시는 경우엔 당행과 수납체결이 되어있지 않더라도 등록금납부는 가능합니다. 당행과 수납체결 여부는 등록금납부 통지서상의 수납은행으로 확인하실 수 있으며, 해당 등록금 납부 메뉴에서 납부하고자하는 학교명이 확인되지 않을경우 당행과 수납체결된 학교는 아닙니다",,신한은행 139 | 33,홈페이지 비밀번호를 5회이상 틀렸습니다. 재설정하려면 어떻게 하죠?,"홈페이지 비밀번호를 5회 오류의 경우 홈페이지 로그인이 불가능하오니 본인인증 절차 후 비밀번호를 새로 등록하시면 이용 가능합니다. 신한은행 홈페이지 접속 후 ① (오른쪽 상단)고객센터 회원서비스 ② 회원정보변경 비밀번호 찾기(재설정) ③ (개인회원)약관보기 및 동의 ④ 본인확인(이름, 생년월일, 아이디, 계좌번호, 계좌비밀번호 입력) ⑤ 사용할 비밀번호/비밀번호 확인 2회 입력확인 ※ 영문, 숫자, 특수문자 포함 8-15자리 이내 ⑥ 비밀번호 재설정 완료 - ID/PW 로그인 화면 하단 비밀번호 재설정 메뉴를 통해서도 진행 가능합니다. 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",홈페이지,신한은행 140 | 32,휴대폰통지서비스 신청 방법은?,휴대폰 통지서비스는 본인의 금융거래내역을 거래발생 즉시 등록된 이동통신 단말기로 통지해 주는 서비스입니다. 휴대폰통지서비스 신청방법 ① 개인뱅킹 로그인 ② 뱅킹보안서비스 ③ 입출내역통지서비스 ④ S알리미 서비스와 입출내역 SMS통지서비스 중 선택 휴대폰통지서비스 특징 S알리미 서비스 : 무료서비스 / 스마트폰 앱 다운로드후 사용가능 입출내역 SMS통지서비스 : 유료서비스 / 핸드폰으로 SMS 발송,,신한은행 141 | 31,보안카드 번호 입력은 어떻게 하면 되죠?,씨크리트(보안)카드는 고객님이 은행에서 신한온라인서비스(인터넷뱅킹)에 가입하신 후 받은 카드입니다. 보안카드번호가 필요한 모든 거래에 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.,인터넷뱅킹,신한은행 142 | 30,즉시/예약 이체 서비스가 안되는 경우 어떻게 해야해?,"이체서비스가 되지 않는 경우 여러가지 사유가 있을수 있으니, 신한은행 고객상담센터 1599-8000번으로 문의 주시면 자세히 상담해드리겠습니다.",인터넷뱅킹,신한은행 143 | 29,신탁적금(당행)으로 이체시 토요일 인터넷뱅시간 제한하는 방법은?,"""우측 상단 QUICK MENU의 이용안내를 참고하시기 바랍니다. ",인증서,신한은행 144 | 28,타행에서 이미 인증서가 발급되었다는데 인증서가 없어요.,공동인증서는 발급기관별 용도별 1개의 인증서만 발급이 가능합니다. 타기관에서 발급받은 인증서가 당행 홈페이지 또는 신한 쏠(SOL)에서 확인되지 않는 경우 아래 내용 체크해 주시기 바랍니다. ① 인증서 정상 저장 여부 확인 ② 인증서의 만료일자(유효기간) 경과여부 확인 ③ 보유하신 인증서의 용도 확인 -은행/신용카드/보험용 또는 전자거래범용 인증서만 사용 가능합니다. ※ 이상이 없으신 경우 정확한 내용 확인을 위해 신한은행 고객상담센터(☎1599-8000)로 전화주시기 바랍니다.,,신한은행 145 | 27,공동인증서 암호가 틀리다고 하는데요?,"인증서 비밀번호는 암호화처리 되어 있어 확인이 불가능합니다. 인증서 비밀번호 오류가 계속 발생된다면 신한 쏠, 개인인터넷뱅킹 인증서 발급/재발급 메뉴에서 재발급을 진행해 주시기 바랍니다.",인증서,신한은행 146 | 26,인터넷뱅킹에서 인증서 암호를 변경하려면 어떻게 해야 하나요?,"공동인증서 암호 변경은 인증서 관리 화면에서 변경가능합니다. 147 | 148 | [신한은행 홈페이지]> 인증센터> 공동인증서(구 공인인증서) > 인증서 관리 > (인증서관리창에서)암호변경 할 인증서 선택 > (하단)암호변경버튼 클릭 > 기존암호, 새비밀번호 2회 입력 149 | 150 | ※ 인증서 암호변경 시 현재 사용중인 암호를 반드시 알고 있어야 변경가능합니다. 만약 사용중인 암호를 분실 하셨을 경우에는 인증서 발급/재발급 화면에서 인증서를 재발급 후 이용해 주시기 바랍니다.",,신한은행 151 | 25,공동인증서 만기 또는 갱신 안내,"신한은행에서 공동인증서를 발급받은 경우 이미 인증서 유효기간이 경과되었다면, 별도의 절차없이 인증센터에서 '인증서 발급/재발급'으로 진행하시면 됩니다. 만약, 유효기간이 한 달 이내로 남았다면 인증센터에서 '인증서 갱신'을 통해 유효기간 갱신 후 이용해 주시기 바랍니다. (재발급을 받으시면 기존 만료일자로 인증서가 재발급됩니다.) 타은행/타기관에서 인증서를 발급받은 경우 인증서를 발급받은 은행 또는 기관에서 재발급을 받으시거나 갱신을 하셔야 합니다. 신한은행 인증센터에서 해당 거래를 실행하실 경우에는 이미 다른 기관에서 인증서를 발급받으셨다는 오류메시지가 나오게 됩니다.",인증서,신한은행 152 | 24,인터넷뱅킹 자동이체해지 방법안내,"인터넷뱅킹에 로그인하신후, 좌측의 메뉴항목에서 이체 > 자동이체 > 자동이체조회 변경/취소 서비스를 통해 해지하실 수 있습니다.",인터넷뱅킹,신한은행 153 | 23,인터넷뱅킹을 통한 이체에 대해 영수증은 없습니다. 라고 나와요.,"영수증이란 돈을 받은 사람이 돈을 준 사람에게 써 주는 증서인데, 송금이체의 경우 은행은 고객의 돈을 받아 고객이 지정하는 계좌로 자금을 보내드리기 때문에 영수증을 발행 할 수 없습니다. 다만, 은행에서 이체송금시 수수료를 수취하기 때문에 수수료에 대해서는 영수증을 발행해 드립니다. (실제로 지점을 방문하셔서 무통장입금으로 이체하실 경우에도 은행에서는 '무통장입금증 타행환입금의뢰확인증'만을 확인서로 제공해드립니다. 이것이 송금금액 전체에 대해 영수증의 의미를 갖는 것은 아니며, 수수료에 대해서만 영수증으로 확인의 의미를 갖습니다) 154 | 특히 인터넷이체의 경우 인터넷상으로 조회되는 입금확인증의 경우 위변조의 가능성이 있기 때문에 거래상대방으로부터 법적 증거력을 얻지 못하고 있는 실정입니다. 155 | 156 | 따라서 고객님이 인터넷뱅킹에 접속하여 조회하시는 인터넷뱅킹거래명세는 영수증으로는 사용될 수 없으며, 거래의 참고용으로만 사용하실 수 있습니다. (조회순서 : 개인인터넷뱅킹 로그인 → (화면 상단) 이체 → 즉시이체 → 이체결과조회 메뉴에서 상세내역 조회후 출력하실 이체건의 오른쪽에 있는 [인쇄] 버튼 클릭하시면 됩니다. 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",,신한은행 157 | 22,인터넷 예적금 해약하려면 어떻게 해야 하나요?,"인터넷에서 신규하셨고, 이후 통장발급을 받지 않으셨다면 인터넷뱅킹(http://bank.shinhan.com)의 금융상품 예금/신탁 해지 메뉴를 통해 해지하실 수 있습니다.",,신한은행 158 | 21,인터넷뱅킹 비밀번호를 잊어버렸습니다.,"인터넷뱅킹 로그인시 사용되는 인증서 암호를 잊어버리셨다면, 영업점 방문없이 인증서 재발급을 통해 인증서 암호를 다시 설정하실수 있습니다. 당행에서 발급한 인증서인 경우 당행 홈페이지에서 재발급하시면 됩니다. 신한은행 홈페이지 인증센터로 접속하시면 금융인증서와 공동인증서구 공인인증서로 메뉴가 분리되어 있으므로 해당 메뉴를 선택해서 접속해 주시면 됩니다. 타기관에서 발급된 인증서인 경우엔 해당기관에서 재발급후, 당행 홈페이지 타기관 공인인증서 등록/해제 메뉴에서 등록 후 이용하시면 됩니다.",인터넷뱅킹,신한은행 159 | 20,타행 이체수수료를 면제받으려면 어떻게해?,"현재 당행에서는 수수료와 관련해서, 신한은행에서 신한은행으로의 송금은 누구나 무료로 이용하실 수 있습니다. 신한은행 우수고객 분들을 대상으로 Tops Club 제도를 운영중에 있으며, Tops Club 고객으로 선정된 고객님께서는 수수료 면제 및 감면 등 다양한 혜택을 드리고 있습니다. 또한 계좌 보유만으로도 별도 우대요건 충족없이 수수료 면제 가능한 쏠편한 입출금통장이 있습니다. 입출금통장을 이미 보유하고 계시다면 기보유중인 상품을 우대통장으로 전환하시거나 쏠편한입출금통장을 추가발급 받는 방법이 있습니다 1. 우대계좌 전환 ①신한 쏠(SOL) 로그인 → 오른쪽 위 전체메뉴(≡) → 조회/관리 → 계좌관리 → 입출금 → 우대계좌 전환 ②인터넷뱅킹 로그인 → 금융상품 → 예금/신탁 → 입출금계좌전환 ③고객센터 상담사 통해서 전환도 가능합니다. 2. 신한 쏠을 통해서 쏠편한입출금통장 신규 가입하는 방법도 있습니다. 구체적인 내용은 예금주 본인께서 신한은행 고객센터 1577-8000으로 연락주시면 자세한 상담이 가능합니다.",인터넷뱅킹,신한은행 160 | 19,타행에서 인증서를 발급받고 신한은행에 등록하는 방법 알려줘.,"신한은행 인터넷뱅킹에 가입이 되어 있다면, 타행에서 발급받은 인증서를 신한은행에 등록하여 이용이 가능합니다. 홈페이지나 신한 쏠(SOL)의 인증센터에서 진행 가능합니다.",인증서,신한은행 161 | 18,인터넷뱅킹의 출금계좌 추가 삭제 방법 알려줘.,"인터넷뱅킹, 신한쏠(SOL)에서 출금계좌의 추가/삭제가 가능합니다. 단, 인터넷뱅킹 신청시 또는 그 이후에 영업점에서 ""출금계좌 인터넷추가등록 동의"" 를 하신 경우에는 인터넷뱅킹, 신한 쏠 에서 추가등록이 가능하며 동의하지 않은 경우엔 신한 쏠 에서 비대면으로 신분증 확인후 출금계좌 추가등록이 가능합니다. (출금계좌 삭제는 동의여부와 관계없이 모두 가능함) 162 | [경로] 163 | - 인터넷뱅킹 로그인→ 사용자관리→ 계좌관리→ 출금계좌관리 164 | - 신한 쏠(SOL) 로그인→전체메뉴→이체→이체관리→출금계좌관리 165 | 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",인터넷뱅킹,신한은행 166 | 17,인터넷뱅킹 해지 방법 알려주세요,[경로] - 인터넷뱅킹 로그인 → 사용자관리 → 인터넷뱅킹관리 → 인터넷뱅킹해지 ☞ 신한은행에서 발급한 인증서 폐기여부 선택가능합니다. ☞ 쏠(모바일뱅킹) 도 사용할수 없음 - 신한 쏠(SOL) 로그인 → 전체메뉴 → 설정/인증 → 서비스 해지 ☞ 쏠(SOL)만 해지 또는 인터넷뱅킹&쏠(SOL) 모두 해지 중 선택하여 해지가능합니다. 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.,인터넷뱅킹,신한은행 167 | 16,수표번호를 조회하고자 합니다.,"수표조회는 신한은행 홈페이지 초화면의 간편서비스 또는 인터넷뱅킹의 조회서비스를 통해 조회하실 수 있습니다. ※ 참고 : 타행 수표의 자금화 타행 수표의 자금화(현금화) 일자는 수표 입금 후 익영업일 오후 12시 20분입니다. 즉 오늘 수표를 통장에 입금하시면 내일(익영업일) 오후 12시 20분 이후에 현금으로 찾으실 수 있습니다. ※ 단, 영업일 기준이므로 휴일(토/일요일, 공휴일) 전날에 입금하시면 휴일 다음날 오후 12시 20분 이후에 현금으로 찾으실 수 있습니다.",인터넷뱅킹,신한은행 168 | 15,인터넷뱅킹 자동이체 신청 및 해지 방법을 알려주세요,"자동이체(기일이체)는 일정기간동안 주기적(매월,3개월,6개월)으로 지정하신 계좌로 이체하실때 편리한 서비스입니다. 당행간 자동이체는 이체 지정일 1영업일 전까지 등록하여야 하며, 타행간 자동이체는 이체 처리일 2영업일 전까지 등록하여야 합니다.자동이체 작업은 은행 영업시간 끝난 후 이루어지며, 당행의 일정에 따라 정확한 시간이 결정됩니다. 그러므로 영업시간 중에 지급계좌에 잔액이 있어야 합니다. 자동이체 신청 및 변경/해지는 당행 인터넷뱅킹을 통해 가능하며, 인터넷뱅킹에 로그인하신 후 ""이체 > 자동이체"" 서비스를 선택하시면 됩니다. ※ 주의 : 타행으로 자동이체는 '이체일 전일'에 자금을 미리 인출하므로 예금잔액이 없으면 이체처리가 안되며, 이후잔액이 채워져도 재처리를 하지 않습니다.",인터넷뱅킹,신한은행 169 | 14,인증서 암호를 분실했거나 변경하고 싶은 경우에는 어떻게 하나요?,"[인증서암호를 분실했을 경우] 170 | 인증센터에서 인증서를 재발급 받으시면서 암호 재설정 가능합니다. 재발급은 신규발급절차와 동일하게 진행됩니다. 171 | [인증서암호를 변경하고 싶은 경우] 172 | 현재 사용중인 암호를 알고 있는 경우 인증센터의 '인증서 관리' 화면에서 인증서암호를 변경하실 수 있습니다. (단, 현재 이용 중인 인증서 암호를 모르실 경우 인증서 재발급 후 이용해 주시기 바랍니다. )",인증서,신한은행 173 | 13,공동인증서암호는 대소문자를 구분하나요?,"인증서암호는 영문 대소문자를 구분합니다. [공동인증서 암호 설정 시 유의사항] - 숫자, 영문, 특수문자 포함해서 10자리이상 30자리 이하로 설정 가능 - 영문 대/소문자를 구분함 - 연속 또는 반복된 3자리 문자, 숫자 사용불가합니다.",인증서,신한은행 174 | 12,"PC포맷, 인증서 삭제 등으로 인증서를 분실하였을 때는?","기존에 사용하시던 공동인증서를 폐기하지 않은 채, 컴퓨터를 포맷하거나 인증서를 삭제하시는 경우의 대처방안입니다. 1.신한은행에서 인증서를 발급받으신 경우① 인터넷뱅킹 : 신한은행 홈페이지 공동인증서(구 공인인증서) 인증서 발급/재발급 메뉴에서 재발급 받으시면 됩니다.② 신한 쏠(SOL) : 신한 쏠(SOL) 메뉴 설정/인증 공동인증서 인증서 발급/재발급 메뉴에서 재발급 가능합니다.※ 신한 쏠(SOL)에서는 은행/신용카드/보험용 인증서만 발급/재발급이 가능합니다.2.타행에서 인증서를 발급받으신 경우 타행에서 인증서를 발급받으신 경우, 발급받으신 은행에서 인증서를 재발급 받으시고 신한은행에 타기관인증서 등록 후 이용부탁드립니다.",인증서,신한은행 175 | 11,인터넷뱅킹을 이용하려면 어떤 PC환경을 갖춰야 합니까?,"인터넷뱅킹(bank.shinhan.com) 176 | 운영체제 177 | 윈도우 : Window7 이상 178 | 맥킨토시 : OSX10.7 이상 179 | 리눅스 : ubuntu, fedora 180 | 브라우저 181 | 윈도우 : IE10이상, 크롬, 파이어폭스, 오페라, Edge 182 | 맥킨토시 : 사파리, 크롬, 파이어폭스 183 | 리눅스 : 크롬, 파이어폭스 184 | 권장해상도 : 1280 X 1024",인터넷뱅킹,신한은행 185 | 10,인터넷뱅킹을 하고자 하는데 인증서는 어디서 발급 하나요?,"인증서는 신한은행에 인터넷뱅킹서비스가 가입되어 있으시면 신한은행 홈페이지 또는 신한 쏠(SOL)의 인증센터에서 발급 가능합니다. ※ 개인은 주민번호로 금융기관 통합하여 발급기관별, 용도별로 한 개의 인증서만 발급이 가능합니다. 예) 타은행에서 은행/신용카드/보험용(결제원) 인증서를 발급받은 경우 → 당행 은행/신용카드/보험용(결제원) 인증서 발급 불가",인터넷뱅킹,신한은행 186 | 9,개인정보 변경은 어디서 해야 하나요?,"인터넷뱅킹에 로그인 하신 후 ""사용자관리""를 클릭하신 후 ""고객정보"" > ""고객정보조회/변경"" 에서 개인정보를 변경하실 수 있습니다.",인터넷뱅킹,신한은행 187 | 8,보안메일서비스 안내해줘,신한은행은 이메일서비스를 통해 고객님의 거래정보와 금융정보를 메일로 알려드리는데 고객님께 발송되는 이메일 중 개인정보보호가 필요한 메일(거래정보 등)은 암호화 처리되어 보안메일로 발송되어 일반메일 (홍보 및 안내메일) 과 구별됩니다. ※ 입출내역 통지서비스는 개인뱅킹 > 뱅킹보안서비스 > 통지서비스 > 입출내역 Email통지서비스 메뉴에서 서비스 신청 및 변경이 가능합니다.,인터넷뱅킹,신한은행 188 | 7,인터넷으로 신규 예/적금 신청하는 방법을 알려주세요,인터넷상으로 예금/신탁을 신규가입하시려면 우선 고객님께서는인터넷뱅킹에 가입하셔야 하며 신규방법은 두 가지가 있습니다.1. 인터넷뱅킹에서 가입인터넷뱅킹 로그인을 하신 후 예금/신탁 > 신규 메뉴에서 예금 및 신탁 상품을 신규하실 수 있습니다.2. 신한S뱅크에서 가입신한S뱅크 상품센터 > 예금센터 메뉴에서 예금상품을 신규하실 수 있습니다.,인터넷뱅킹,신한은행 189 | 6,인터넷뱅킹 송금 수수료는 얼마죠?,인터넷뱅킹(bank.shinhan.com) 송금 수수료는 우측 퀵메뉴 이용안내 수수료/이용시간안내 인터넷뱅킹 이용수수료 메뉴에서 확인 가능합니다.,인터넷뱅킹,신한은행 190 | 5,인터넷에서 이체한 거래의 상세내역 및 영수증을 인쇄하고 싶어요,"인터넷상에서 이체한 거래의 경우 인터넷상에서 제공하는 영수증은 없으며, 거래의 참고용으로 단지 조회일자 및 출금 계좌번호 등으로 상세내역(이체 확인증 목록)을 조회하거나 그 내역을 인쇄만 가능합니다. 인터넷뱅킹 거래 상세내역(이체확인증 목록)및 입금증(이체확인증) 인쇄 방법은 다음과 같습니다. ① 인터넷뱅킹에 접속하여 로그인 ② 이체 → 즉시이체 → 이체결과조회 ③ 조회구분, 출금계좌번호, 조회일자등 상세내역을 조회 ④ 입금확인증목록을 인쇄하시려면 조회된 내역 왼쪽 상단에 보이는 [이체목록인쇄] 버튼을 클릭하면 해당 거래내역을 출력할 수 있으며, 입금확인증을 인쇄하시려면 해당 이체건의 오른쪽에 있는 [인쇄] 버튼을 클릭하시면 됩니다. ※ 참고 : 즉시이체 후에도 이체확인증 목록 및 이체확인증을 출력하실 수 있습니다.",인터넷뱅킹,신한은행 191 | 4,타행으로 자동이체를 신청하고 싶은데요,"인터넷뱅킹을 통한 타은행 계좌로의 자동이체(납부자 자동이체)신청이 가능하며 수수료는 건당 300원 입니다. 자동이체를 신청하시려면 인터넷뱅킹에 로그인 하신후 [이체] > [자동이체] > [자동이체 등록] 메뉴를 선택하시면 타행으로의 자동이체 등록을 하실 수 있습니다. ※ 주의: 타은행 자동이체는 이체 지정일 전영업일에 고객님의 계좌에서 인출하여 이체지정일자(지정일이 휴일인 경우에는 익영업일)에 타은행 계좌로 입금됩니다. 그러므로 예금잔액이 없으면 이체처리가 안되며, 이후잔액이 채워져도 재처리를 하지 않습니다. 따라서 고객님께서 자동이체를 신청하실 때에는 충분한 잔액이 있는지 확인하시기 바랍니다.",인터넷뱅킹,신한은행 192 | 3,공과금 자동이체 신청이 가능한가요?,"인터넷뱅킹에 로그인하신 후 ""공과금/법원 > 공과금센터"" 페이지에 가시면 ""지로자동이체 등록"" 메뉴가 있습니다. 해당 메뉴를 통하여 ""전기요금, 전화요금, 국민연금, 국민건강보험료 등을 포함하여 각종 지로요금""을 모두 자동이체 등록하실 수 있습니다.",인터넷뱅킹,신한은행 193 | 2,인터넷뱅킹 거래에 대한 책임 범위를 알고싶습니다.,"기본적으로 은행이 인터넷뱅킹 사고에 대한 책임을 집니다. 해커의 침입에 의해 자금이 불법적으로 유출되었다고 하더라도 이에 대한 책임은 은행에 있습니다. 그러나 다음의 경우는 고객께서 책임을 지게 됩니다. 계좌번호를 잘못 입력하여 입금함으로써 자금이 의도하지 않은 계좌로 유출된 경우, 인증서암호, 이체비밀번호, 씨크리트카드 등의 관리를 소홀히 하여 타인의 도용에 의해 자금이 불법으로 유출된 경우, 거래도중 자리를 떠나서 타인이 불법으로 자금을 타계좌로 유출한 경우 (Log on 상태에서 자리를 떠나는 것은 대단히 위험하므로 반드시 거래 종료 후 자리를 떠나시기 바랍니다), 사고를 인지하였음에도 사고신고를 하지 않아 신속한 대처를 하지 못하게 방치한 경우",인터넷뱅킹,신한은행 194 | 1,인터넷뱅킹을 이용하려면 어떻게 해야 합니까?,"영업점에 방문하지 않아도, 모바일에서 신한 쏠 (SOL) 어플 다운로드후 회원가입하시면 인터넷뱅킹/모바일뱅킹 가입이 가능합니다. ※ 만 14세 미만의 경우 법정대리인(부모/친권자/후견인)에 의해 영업점 방문하여 가입가능함 기타 궁금하신 내용은 신한은행 고객센터 1599-8000로 문의하여 주시기 바랍니다.",인터넷뱅킹,신한은행 -------------------------------------------------------------------------------- /RAG-SageMaker/images/TensorShard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/TensorShard.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/architecture-rag-opensearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/architecture-rag-opensearch.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open1.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open2.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open3.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open4.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open5.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open6.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open7.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/open8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/open8.png -------------------------------------------------------------------------------- /RAG-SageMaker/images/rag-lang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/images/rag-lang.png -------------------------------------------------------------------------------- /RAG-SageMaker/indexer/fsi_faq_indexer_ko/index.faiss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/indexer/fsi_faq_indexer_ko/index.faiss -------------------------------------------------------------------------------- /RAG-SageMaker/indexer/fsi_faq_indexer_ko/index.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/RAG-SageMaker/indexer/fsi_faq_indexer_ko/index.pkl -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/README.md: -------------------------------------------------------------------------------- 1 | # rag-fsi-data-workshop 2 | 3 | ## Retrieval Augmented Question & Answering with Amazon SageMaker and Opensearch using LangChain 4 | 5 | 6 | 7 | 8 | 이 실습에서는 SageMaker Endpoint와 SDK, 그리고 [LangChain](https://python.langchain.com/docs/get_started/introduction) 및 [FAISS](https://faiss.ai/index.html)와 같은 오픈소스 소프트웨어를 통해 이러한 패턴을 구현하는 실무 경험을 쌓을 수 있습니다. 9 | 10 | 11 | ## 개요 12 | RAG (Retrieval-Augmented Generation)는 최신의 자연어 처리 (NLP) 분야에서 많은 관심을 받고 있는 아키텍처입니다. 기본적으로, RAG는 문제 해결을 위한 정보를 검색(retrieval)하고 그 정보를 바탕으로 문장을 생성(generation)하는 두 가지 과정을 통합합니다. 13 | 이 실습에서는 RAG 아키텍처가 어떻게 Context 기반의 프롬프트 확장을 가능하게 하는지에 대해 설명합니다. 또한, RAG가 어떻게 Amazon Opensearch와 통합되어 외부의 신뢰할 수 있는 데이터베이스나 문서를 검색하는 과정을 강화하는지에 대해 실습합니다. 14 | 15 | 16 | ## Context 기법의 확장 17 | 전통적인 Seq2Seq (Sequence-to-Sequence) 모델은 주어진 입력에 대해 출력을 생성하기 위해 고정된 수의 토큰을 사용합니다. 그러나 RAG는 이러한 접근 방식을 확장하여 다양한 문맥 정보를 수집하고 활용할 수 있습니다. 이러한 확장성은 프롬프트 엔지니어링에 큰 유리함을 제공합니다. 18 | 19 | ### RAG의 프롬프트 확장 20 | 21 |
22 | image 2 23 |
24 | 25 | 프롬프트 확장이란, 사용자가 제공하는 질문이나 명령어에 대한 반응을 개선하기 위한 방법입니다. RAG를 사용하면, 모델은 문맥에 따라 다양한 외부 정보를 검색할 수 있으며, 이를 통해 보다 정확하고 상세한 응답을 생성할 수 있습니다. 26 | 27 | 예를 들어, 사용자가 "세계에서 가장 높은 산은 무엇인가요?"라고 물을 경우, 일반적인 Seq2Seq 모델은 사전 학습된 지식만을 바탕으로 답변을 생성합니다. 그러나 RAG 모델은 외부의 신뢰할 수 있는 데이터베이스나 문서를 검색하여, 현재까지 알려진 가장 정확한 정보를 제공할 수 있습니다. 28 | 29 | ### RAG의 주요 구성 요소 30 | 31 | - **문제 질의 (Query)** 32 | 사용자가 특정 질문이나 문제를 제시합니다. 33 | 34 | - **검색 엔진 (Retriever)** 35 | 주어진 질의에 따라 관련된 문서나 정보를 데이터베이스에서 검색합니다. 36 | - Amazon OpenSearch의 Faiss vector store를 활용합니다. 37 | - Faiss의 임베딩 검색은 [`similarity_search_with_score`](https://python.langchain.com/docs/integrations/vectorstores/faiss) 함수를 사용하여 L2 Norm을 기준으로 유사도를 계산합니다. 38 | 39 | - **순위 매기기 (Ranking)** 40 | 검색된 정보를 관련성이 높은 순으로 정렬합니다. 41 | - 로컬 Faiss 검색의 경우, L2 distance search를 사용하며, 값이 클수록 높은 에러를 나타냅니다. 42 | - OpenSearch에서는 Faiss의 ranking score를 정규화하여, 값이 클수록 높은 유사도를 나타냅니다. 43 | 44 | - **생성 모델 (Generator)** 45 | 정렬된 문서나 정보를 기반으로 최종 답변을 생성합니다. 46 | - Ployglot 12.8B 또는 5.8B 한국어 LLM (KULLM 모델)을 사용합니다. 47 | 48 | - **응답 (Output)** 49 | 생성된 답변이 프롬프트 엔지니어링을 거쳐 문장 형태로 사용자에게 반환됩니다. 50 | 51 | ### RAG와 Amazon Opensearch의 통합 52 |
53 | image 1 54 |
55 | 56 | RAG는 주어진 문맥 또는 프롬프트를 더 잘 이해하고 응답하기 위해 외부 정보를 검색합니다. Amazon Opensearch의 통합은 이 과정을 더욱 강화합니다. Amazon Opensearch를 사용하면, 대규모 데이터를 효율적으로 처리할 수 있습니다. 이를 통해 RAG는 더욱 다양한 문서와 데이터를 검색하여 응답을 생성할 수 있습니다. 57 | Amazon Opensearch의 통합은 RAG 아키텍처의 정보 검색 능력을 더욱 강화할 수 있습니다. RAG를 활용한 LLM은 더욱 다양하고 신뢰할 수 있는 응답을 생성할 수 있게 되어, NLP 분야에서의 응용 가능성이 더욱 확장됩니다. 58 | 59 | 60 | ## 한국어 금융 QnA 챗봇 모델 생성 가이드 61 | 62 | ### Step 1. SageMaker Endpoint에 Embedding Vector 모델 배포 63 | [보기 - TASK-1_Embedding_Vector_Model_Creation.ipynb](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-1_Embedding_Vector_Model_Creation.ipynb) 64 | 65 | 1. AWS SageMaker Studio 콘솔에 로그인합니다. 66 | 2. SageMaker Studio 로컬에서 embedding tokenizer를 테스트 해봅니다. 67 | 3. SageMaker Endpoint에 Embedding Vector 모델을 선택하고 배포를 시작합니다. 68 | 4. 모델 배포가 완료되면, 생성된 Endpoint를 확인합니다. 69 | 70 | ### Step 2. SageMaker Endpoint에 Ployglot 한국어 LLM 5.8B(이벤트엔진계정의 경우) or 12.8B 배포 71 | [보기 - TASK-2_Polyglot_5.8B_Korea_LLM_Model_Creation.ipynb](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-2_Polyglot_5.8B_Korea_LLM_Model_Creation.ipynb) 72 | [보기 - TASK-2-optional_Polyglot_12.8B_Korea_LLM_Model_Creation.ipynb](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-2-optional_Polyglot_12.8B_Korea_LLM_Model_Creation.ipynb) 73 | 74 | 1. SageMaker 콘솔로 돌아가서 새 모델을 생성합니다. 75 | 2. Polyglot 한국어 LLM 5.8B (이벤트엔진 계정의 경우) 또는 12.8B를 선택합니다. (실습 이벤트엔진 계정에서 배포할 수 있는 5.8B모델의 경우 G5.2xlarge GPU1개 인스턴스에서 생성되나 LLM의 성능은 12.8B에 비해 떨어집니다.) 76 | 3. SageMaker Endpoint에 한국어 Polyglot LLM 모델 배포를 시작합니다. 77 | 4. 배포가 완료되면 새로운 Endpoint를 확인한 다음, 문장요약 테스트를 합니다. 78 | 79 | ### Step 3. 한국어 금융 Question & Answering 데이터 로컬 임베딩 검색 테스트 80 | [보기 - TASK-3_FSI_FAQ_Faiss_Vector_Search_Local_Store_Test.ipynb](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-3_FSI_FAQ_Faiss_Vector_Search_Local_Store_Test.ipynb) 81 | 82 | 1. SageMaker Studio 로컬 환경에서 한국어 금융 QnA 데이터셋을 준비합니다. 83 | 2. 앞서 생성한 Embedding Vector 모델의 Endpoint를 사용하여 데이터를 임베딩합니다. 84 | 3. 임베딩된 데이터를 Studio 로컬에서 로드한 다음 검색 RAG 테스트를 진행합니다. 85 | 86 | ### Step 4. SageMaker Opensearch 생성 및 인덱스에 금융 FAQ 임베딩 데이터 입력 검색 테스트 87 | [보기 - TASK-4_OpenSearch_Creation_and_Vector_Insertion.ipynb](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-4_OpenSearch_Creation_and_Vector_Insertion.ipynb) 88 | 89 | 1. AWS 콘솔에서 SageMaker Opensearch 서비스를 찾아 들어갑니다. 90 | 2. 새 Opensearch 도메인을 생성합니다. 91 | 3. 앞서 임베딩한 금융 FAQ 데이터를 SageMaker에 배포된 Embedding Vector 모델 Endpoint를 이용하여 벡터형식으로 Opensearch 인덱스에 입력합니다. 92 | 4. 인덱스에 데이터 입력이 완료되면 RAG 검색 테스트를 진행합니다. 93 | 94 | 95 | 96 | ### Step 5. Streamlit으로 QnA 챗봇 모델 생성해보기 97 | [보기 - TASK-5_OpenSearch_LLM_RAG_Streamlit_Chatbot_Example.py](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/blob/main/RAG-SageMaker/rag-fsi-data-workshop/TASK-5_OpenSearch_LLM_RAG_Streamlit_Chatbot_Example.py) 98 | 1. SageMaker Studio의 Jupyter Lab에서 Terminal을 엽니다. 99 | 2. Terminal 환경에서 Streamlit관련 패키지들을 설치합니다. 100 | ```sh 101 | pip install -r /home/sagemaker-user/AWS-LLM-SageMaker/RAG-SageMaker/rag-fsi-data-workshop/requirements.txt 102 | 103 | ``` 104 | 4. Streamlit 앱 파일을 오픈하고, SageMaker Embedding Vector 모델, Ployglot LLM 모델, opensearch_domain_endpoint 정보를 입력 수정합니다. 105 | 5. Streamlit을 실행해봅니다. 106 | ```sh 107 | streamlit run TASK-5_OpenSearch_LLM_RAG_Streamlit_Chatbot_Example.py 108 | ``` 109 | 6. QnA 챗봇 로직은 알맞게 수정해봅니다. 110 | 7. 앱을 실행하여 챗봇 모델이 잘 동작하는지 테스트합니다. 111 | ``` text 112 | Studio의 Jupyter Lab 도메인 URL과 유사한 URL을 사용하여 새 브라우저 탭에서 앱에 액세스할 수 있습니다. 예를 들어 Jupyter Lab URL이 113 | https://t0r5tpibtvoywyw.studio.us-east-1.sagemaker.aws/jupyterlab/default/lab? 114 | 인 걍우 Streamlit 앱의 URL은 115 | https://t0r5tpibtvoywyw.studio.us-east-1.sagemaker.aws/jupyterlab/default/proxy/8501/ 입니다. 116 | (lab이 proxy/8501/로 대체됩니다. 8501/ 마지막 슬레시를 꼭 붙여줍니다.) 이전 단계에서 확인된 포트 번호가 8501과 다른 경우 Streamlit 앱의 URL에 8501 대신 해당 포트 번호를 사용하세요. 117 | ``` 118 | -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/TASK-0_Setup.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "26cfe671-8862-413b-80b1-38a527c0f323", 7 | "metadata": { 8 | "tags": [] 9 | }, 10 | "outputs": [], 11 | "source": [ 12 | "!pip install -U pip" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "5786fbf3-8783-4e70-884a-f60bf28147df", 19 | "metadata": { 20 | "tags": [] 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "!pip install -U sagemaker transformers boto3 huggingface_hub sagemaker langchain deepspeed accelerate" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "e35446b4-48bc-4bda-b0e9-7623d4931a46", 31 | "metadata": { 32 | "tags": [] 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "# Version fixed management during code stabilization period of langchain\n", 37 | "!pip install SQLAlchemy==2.0.1" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "id": "ddb6ce46-fa57-45bb-a032-60c9cb474e82", 44 | "metadata": { 45 | "tags": [] 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "!pip install -U faiss-cpu opensearch-py" 50 | ] 51 | } 52 | ], 53 | "metadata": { 54 | "kernelspec": { 55 | "display_name": "conda_pytorch_p310", 56 | "language": "python", 57 | "name": "conda_pytorch_p310" 58 | }, 59 | "language_info": { 60 | "codemirror_mode": { 61 | "name": "ipython", 62 | "version": 3 63 | }, 64 | "file_extension": ".py", 65 | "mimetype": "text/x-python", 66 | "name": "python", 67 | "nbconvert_exporter": "python", 68 | "pygments_lexer": "ipython3", 69 | "version": "3.10.10" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 5 74 | } 75 | -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/TASK-2-optional_Polyglot_12.8B_Korea_LLM_Model_Creation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2662eb8f-ba8b-4c34-8ab9-b312ecfcff91", 6 | "metadata": {}, 7 | "source": [ 8 | "# Korean LLM (Large Language Model) Serving on SageMaker with AWS Large Model Container DLC\n", 9 | "---\n", 10 | "* KULLM-Polyglot-12.8B-v2 모델로써, 130억개의 파라미터를 가진 LLM 모델이며, 최소 G5.12xlarge GPU 4개를 사용하는 모델입니다. \n", 11 | "* (이벤트엔진계정에서는 배포할수없습니다.)\n", 12 | "* 한국어 LLM 모델 SageMaker 서빙 핸즈온 (허깅페이스 허브에서 모델을 그대로 배포)\n", 13 | "- LLM GitHub: https://github.com/nlpai-lab/KULLM\n", 14 | "- Hugging Face model hub: https://huggingface.co/nlpai-lab/kullm-polyglot-12.8b-v2\n", 15 | "- [AWS Blog: Deploy large models on Amazon SageMaker using DJLServing and DeepSpeed model parallel inference](https://aws.amazon.com/ko/blogs/machine-learning/deploy-large-models-on-amazon-sagemaker-using-djlserving-and-deepspeed-model-parallel-inference)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "id": "5a056d19-3339-4778-b73d-f5fe14d50ae0", 22 | "metadata": { 23 | "tags": [] 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "%load_ext autoreload\n", 28 | "%autoreload 2\n", 29 | "import sys\n", 30 | "sys.path.append('../utils')\n", 31 | "sys.path.append('../templates')\n", 32 | "sys.path.append('../common_code')" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 3, 38 | "id": "45ca4169-bc8f-4eeb-b897-b48b15f1c6eb", 39 | "metadata": { 40 | "tags": [] 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "import os\n", 45 | "import sagemaker, boto3, jinja2\n", 46 | "role = sagemaker.get_execution_role() # execution role for the endpoint\n", 47 | "sess = sagemaker.session.Session() # sagemaker session for interacting with different AWS APIs\n", 48 | "bucket = sess.default_bucket() # bucket to house artifacts\n", 49 | "model_bucket = sess.default_bucket() # bucket to house artifacts\n", 50 | "\n", 51 | "region = sess._region_name # region name of the current SageMaker Studio environment\n", 52 | "account_id = sess.account_id() # account_id of the current SageMaker Studio environment\n", 53 | "\n", 54 | "s3_client = boto3.client(\"s3\") # client to intreract with S3 API\n", 55 | "sm_client = boto3.client(\"sagemaker\") # client to intreract with SageMaker\n", 56 | "smr_client = boto3.client(\"sagemaker-runtime\") # client to intreract with SageMaker Endpoints\n", 57 | "jinja_env = jinja2.Environment() # jinja environment to generate model configuration templates" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "id": "85236aea-d0e6-4b5f-89bf-2d185881740c", 63 | "metadata": { 64 | "tags": [] 65 | }, 66 | "source": [ 67 | "
\n", 68 | "\n", 69 | "## 1. Download LLM model and upload it to S3\n", 70 | "---" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 4, 76 | "id": "8a104756-a389-403c-bb3a-b512ed6b948d", 77 | "metadata": { 78 | "tags": [] 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "from huggingface_hub import snapshot_download\n", 83 | "from pathlib import Path\n", 84 | "\n", 85 | "model_id = \"nlpai-lab/kullm-polyglot-12.8b-v2\"\n", 86 | "model_prefix = model_id.split('/')[-1].replace('.', '-')\n", 87 | "\n", 88 | "s3_code_prefix = f\"ko-llm/{model_prefix}/code\" # folder within bucket where code artifact will go\n", 89 | "s3_model_prefix = f\"ko-llm/{model_prefix}/model\" # folder where model checkpoint will go" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "99fca204-ff77-49c5-bc76-b5383c8ded4e", 95 | "metadata": {}, 96 | "source": [ 97 | "
\n", 98 | "\n", 99 | "## 2. Model Serving Scripts\n", 100 | "---\n", 101 | "### Create `serving.properties`\n", 102 | "\n", 103 | "이 설정 파일은 어떤 추론 최적화 라이브러리를 사용할지, 어떤 설정을 사용할지 DJL Serving에 알려주는 설정 파일입니다. 필요에 따라 적절한 구성을 설정할 수 있습니다.\n", 104 | "\n", 105 | "모델이 레이어에 따라 분할되는 파이프라인 병렬화(Pipeline Parallelism)를 사용하는 허깅페이스 Accelerate와 달리, DeepSpeed는 각 레이어(텐서)가 여러 디바이스에 걸쳐 샤딩되는 텐서 병렬화(Tensor Parallelism)를 사용합니다. 파이프라인 병렬 처리 접근 방식에서는 데이터가 각 GPU 장치를 통해 순차적으로 흐르지만, 텐서 병렬 처리는 데이터가 모든 GPU 장치로 전송되어 각 GPU에서 부분적인 결과가 계산됩니다. 그런 다음 All-Gather 연산을 통해 부분 결과를 수집하여 최종 결과를 계산합니다. 따라서, 텐서 병렬화가 일반적으로 더 높은 GPU 활용률과 더 나은 성능을 제공합니다.\n", 106 | "\n", 107 | "- `option.s3url` - 모델 파일의 위치를 지정합니다. 또는`option.model_id` 옵션을 대신 사용하여 허깅페이스 허브에서 모델을 지정할 수 있습니다(예: EleutherAI/gpt-j-6B). 그러면 허브에서 모델이 자동으로 다운로드됩니다. s3url 접근 방식은 자체 환경 내에서 모델 아티팩트를 호스팅할 수 있고 DJL 추론 컨테이너 내에서 최적화된 접근 방식을 활용하여 S3에서 호스팅 인스턴스로 모델을 전송함으로써 더 빠른 모델 배포가 가능합니다.\n", 108 | "\n", 109 | "`serving.properties`의 일반적인 설정법과 자세한 내용은 https://docs.aws.amazon.com/sagemaker/latest/dg/large-model-inference-configuration.html 를 참조하세요.\n", 110 | "\n", 111 | "" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 5, 117 | "id": "7e960108-f6bf-4b8d-9ea6-78b6ca3c6915", 118 | "metadata": { 119 | "tags": [] 120 | }, 121 | "outputs": [], 122 | "source": [ 123 | "src_path = f\"src/{model_prefix}\"\n", 124 | "!rm -rf {src_path}\n", 125 | "os.makedirs(src_path, exist_ok=True)" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 6, 131 | "id": "5beab7e1-205c-47b1-b27a-0120614caec2", 132 | "metadata": { 133 | "tags": [] 134 | }, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "Writing src/kullm-polyglot-12-8b-v2/serving.properties\n" 141 | ] 142 | } 143 | ], 144 | "source": [ 145 | "%%writefile {src_path}/serving.properties\n", 146 | "\n", 147 | "engine=DeepSpeed\n", 148 | "\n", 149 | "# passing extra options to model.py or built-in handler\n", 150 | "job_queue_size=100\n", 151 | "batch_size=1\n", 152 | "max_batch_delay=1\n", 153 | "max_idle_time=60\n", 154 | "\n", 155 | "# Built-in entrypoint\n", 156 | "#option.entryPoint=djl_python.deepspeed\n", 157 | "\n", 158 | "# Hugging Face model id\n", 159 | "#option.model_id={{model_id}}\n", 160 | "\n", 161 | "# defines custom environment variables\n", 162 | "#env=SERVING_NUMBER_OF_NETTY_THREADS=2\n", 163 | "\n", 164 | "# Allows to load DeepSpeed workers in parallel\n", 165 | "option.parallel_loading=true\n", 166 | "\n", 167 | "# specify tensor parallel degree (number of partitions)\n", 168 | "option.tensor_parallel_degree=4\n", 169 | "\n", 170 | "# specify per model timeout\n", 171 | "option.model_loading_timeout=600\n", 172 | "#option.predict_timeout=240\n", 173 | "\n", 174 | "# mark the model as failure after python process crashing 10 times\n", 175 | "retry_threshold=0\n", 176 | "\n", 177 | "option.task=text-generation" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "id": "76217c91-0a17-4895-a400-e8bd9889f00d", 183 | "metadata": {}, 184 | "source": [ 185 | "### Create model.py with custom inference code\n", 186 | "빌트인 추론 코드로 no-code로 배포할 수도 있지만, 커스텀 추론 코드를 작성하는 것도 가능합니다." 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 7, 192 | "id": "ff9c01fd-d6cc-4dfc-9dfe-8a63ebd8cf60", 193 | "metadata": { 194 | "tags": [] 195 | }, 196 | "outputs": [ 197 | { 198 | "name": "stdout", 199 | "output_type": "stream", 200 | "text": [ 201 | "Writing src/kullm-polyglot-12-8b-v2/model.py\n" 202 | ] 203 | } 204 | ], 205 | "source": [ 206 | "%%writefile {src_path}/model.py\n", 207 | "from djl_python import Input, Output\n", 208 | "import os\n", 209 | "import deepspeed\n", 210 | "import torch\n", 211 | "import logging\n", 212 | "from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer\n", 213 | "from transformers import GPTNeoXLayer\n", 214 | "\n", 215 | "predictor = None\n", 216 | "\n", 217 | "def get_model(properties):\n", 218 | " \n", 219 | " tp_degree = properties[\"tensor_parallel_degree\"]\n", 220 | " # model_location = properties[\"model_dir\"]\n", 221 | " # if \"model_id\" in properties:\n", 222 | " # model_location = properties[\"model_id\"]\n", 223 | " model_location = \"nlpai-lab/kullm-polyglot-12.8b-v2\" \n", 224 | " task = properties[\"task\"]\n", 225 | " \n", 226 | " logging.info(f\"Loading model in {model_location}\") \n", 227 | " local_rank = int(os.getenv(\"LOCAL_RANK\", \"0\"))\n", 228 | "\n", 229 | " tokenizer = AutoTokenizer.from_pretrained(model_location)\n", 230 | "\n", 231 | " model = AutoModelForCausalLM.from_pretrained(\n", 232 | " model_location,\n", 233 | " torch_dtype=torch.float16,\n", 234 | " low_cpu_mem_usage=True,\n", 235 | " )\n", 236 | " \n", 237 | " model.requires_grad_(False)\n", 238 | " model.eval()\n", 239 | " \n", 240 | " ds_config = {\n", 241 | " \"tensor_parallel\": {\"tp_size\": tp_degree},\n", 242 | " \"dtype\": model.dtype,\n", 243 | " \"injection_policy\": {\n", 244 | " GPTNeoXLayer:('attention.dense', 'mlp.dense_4h_to_h')\n", 245 | " }\n", 246 | " }\n", 247 | " logging.info(f\"Starting DeepSpeed init with TP={tp_degree}\") \n", 248 | " model = deepspeed.init_inference(model, ds_config) \n", 249 | " \n", 250 | " generator = pipeline(\n", 251 | " task=task, model=model, tokenizer=tokenizer, device=local_rank\n", 252 | " )\n", 253 | " # https://huggingface.co/docs/hub/models-tasks\n", 254 | " return generator\n", 255 | " \n", 256 | "def handle(inputs: Input) -> None:\n", 257 | " \"\"\"\n", 258 | " inputs: Contains the configurations from serving.properties\n", 259 | " \"\"\" \n", 260 | " global predictor\n", 261 | " if not predictor:\n", 262 | " predictor = get_model(inputs.get_properties())\n", 263 | "\n", 264 | " if inputs.is_empty():\n", 265 | " # Model server makes an empty call to warmup the model on startup\n", 266 | " logging.info(\"is_empty\")\n", 267 | " return None\n", 268 | "\n", 269 | " data = inputs.get_as_json() #inputs.get_as_string()\n", 270 | " logging.info(\"data:\", data)\n", 271 | " \n", 272 | " input_prompt, params = data[\"inputs\"], data[\"parameters\"]\n", 273 | " result = predictor(input_prompt, **params)\n", 274 | " logging.info(\"result:\", result)\n", 275 | "\n", 276 | " return Output().add_as_json(result) #Output().add(result)" 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "id": "a063b634-3a4f-49ec-8779-fdda2818326e", 282 | "metadata": {}, 283 | "source": [ 284 | "### Create the Tarball and then upload to S3" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 8, 290 | "id": "90fa1063-839a-46be-9ca4-f6b9c14efbc7", 291 | "metadata": { 292 | "tags": [] 293 | }, 294 | "outputs": [ 295 | { 296 | "name": "stdout", 297 | "output_type": "stream", 298 | "text": [ 299 | "./\n", 300 | "./serving.properties\n", 301 | "./model.py\n" 302 | ] 303 | } 304 | ], 305 | "source": [ 306 | "!rm -rf model.tar.gz\n", 307 | "!tar czvf model.tar.gz -C {src_path} ." 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 9, 313 | "id": "1d60fd8a-8a0e-497c-8628-98eb26772cd9", 314 | "metadata": { 315 | "tags": [] 316 | }, 317 | "outputs": [ 318 | { 319 | "name": "stdout", 320 | "output_type": "stream", 321 | "text": [ 322 | "S3 Code or Model tar ball uploaded to --- > s3://sagemaker-us-east-1-143656149352/ko-llm/kullm-polyglot-12-8b-v2/code/model.tar.gz\n" 323 | ] 324 | } 325 | ], 326 | "source": [ 327 | "s3_code_artifact = sess.upload_data(\"model.tar.gz\", bucket, s3_code_prefix)\n", 328 | "print(f\"S3 Code or Model tar ball uploaded to --- > {s3_code_artifact}\")\n", 329 | "!rm -rf model.tar.gz" 330 | ] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "id": "57ea2358-3ba1-4ff3-9ca4-df772b59770d", 335 | "metadata": {}, 336 | "source": [ 337 | "
\n", 338 | "\n", 339 | "## 3. Serve LLM Model on SageMaker\n", 340 | "\n", 341 | "---" 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "id": "7820b1c3-7854-433d-bbb6-03193abefa22", 347 | "metadata": {}, 348 | "source": [ 349 | "### Create SageMaker Model\n", 350 | "\n", 351 | "SageMaker 엔드포인트 생성 매개변수 VolumeSizeInGB를 지정할 때 마운트되는 Amazon EBS(Amazon Elastic Block Store) 볼륨에 /tmp를 매핑하기 때문에 컨테이너는 인스턴스의 `/tmp` 공간에 모델을 다운로드합니다. 이때 s5cmd (https://github.com/peak/s5cmd) 를 활용하므로 대용량 모델을 빠르게 다운로드할 수 있습니다.\n", 352 | "볼륨 인스턴스와 함께 미리 빌드되어 제공되는 p4dn과 같은 인스턴스의 경우 컨테이너의 `/tmp`를 계속 활용할 수 있습니다. " 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 10, 358 | "id": "598b6ded-ba9c-4f25-b862-090546607b98", 359 | "metadata": { 360 | "tags": [] 361 | }, 362 | "outputs": [ 363 | { 364 | "name": "stdout", 365 | "output_type": "stream", 366 | "text": [ 367 | "kullm-polyglot-12-8b-v2-2023-07-23-14-04-08-969\n", 368 | "Created Model: arn:aws:sagemaker:us-east-1:143656149352:model/kullm-polyglot-12-8b-v2-2023-07-23-14-04-08-969\n" 369 | ] 370 | } 371 | ], 372 | "source": [ 373 | "from sagemaker.utils import name_from_base\n", 374 | "from sagemaker import image_uris\n", 375 | "\n", 376 | "img_uri = image_uris.retrieve(framework=\"djl-deepspeed\", region=region, version=\"0.23.0\")\n", 377 | "model_name = name_from_base(f\"{model_prefix}\")\n", 378 | "print(model_name)\n", 379 | "\n", 380 | "model_response = sm_client.create_model(\n", 381 | " ModelName=model_name,\n", 382 | " ExecutionRoleArn=role,\n", 383 | " PrimaryContainer={\"Image\": img_uri, \"ModelDataUrl\": s3_code_artifact},\n", 384 | ")\n", 385 | "model_arn = model_response[\"ModelArn\"]\n", 386 | "print(f\"Created Model: {model_arn}\")" 387 | ] 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "id": "a96783b7-9e6a-4bed-8ff9-c779d9e628e4", 392 | "metadata": {}, 393 | "source": [ 394 | "### Create SageMaker Endpoint" 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": 11, 400 | "id": "0d2f670c-57f4-4092-af29-b1416829e9dd", 401 | "metadata": { 402 | "tags": [] 403 | }, 404 | "outputs": [ 405 | { 406 | "name": "stdout", 407 | "output_type": "stream", 408 | "text": [ 409 | "Created Endpoint: arn:aws:sagemaker:us-east-1:143656149352:endpoint/kullm-polyglot-12-8b-v2-2023-07-23-14-04-08-969-endpoint\n" 410 | ] 411 | } 412 | ], 413 | "source": [ 414 | "endpoint_config_name = f\"{model_name}-config\"\n", 415 | "endpoint_name = f\"{model_name}-endpoint\"\n", 416 | "variant_name = \"variant1\"\n", 417 | "instance_type = \"ml.g5.12xlarge\"\n", 418 | "initial_instance_count = 1\n", 419 | "\n", 420 | "prod_variants = [\n", 421 | " {\n", 422 | " \"VariantName\": \"variant1\",\n", 423 | " \"ModelName\": model_name,\n", 424 | " \"InstanceType\": instance_type,\n", 425 | " \"InitialInstanceCount\": initial_instance_count,\n", 426 | " # \"ModelDataDownloadTimeoutInSeconds\": 2400,\n", 427 | " \"ContainerStartupHealthCheckTimeoutInSeconds\": 1600,\n", 428 | " }\n", 429 | "]\n", 430 | "\n", 431 | "endpoint_config_response = sm_client.create_endpoint_config(\n", 432 | " EndpointConfigName=endpoint_config_name,\n", 433 | " ProductionVariants=prod_variants\n", 434 | ")\n", 435 | "\n", 436 | "endpoint_response = sm_client.create_endpoint(\n", 437 | " EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name\n", 438 | ")\n", 439 | "print(f\"Created Endpoint: {endpoint_response['EndpointArn']}\")" 440 | ] 441 | }, 442 | { 443 | "cell_type": "markdown", 444 | "id": "0bf78f45-b06e-431c-9048-3ade776cac07", 445 | "metadata": {}, 446 | "source": [ 447 | "엔드포인트가 생성되는 동안 아래의 문서를 같이 확인해 보세요.\n", 448 | "- https://docs.aws.amazon.com/sagemaker/latest/dg/large-model-inference-dlc.html" 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": 12, 454 | "id": "6122a3f8-78b6-42b9-b390-af8942d8e30c", 455 | "metadata": { 456 | "tags": [] 457 | }, 458 | "outputs": [ 459 | { 460 | "data": { 461 | "text/html": [ 462 | " [SageMaker LLM Serving] Check Endpoint Status" 463 | ], 464 | "text/plain": [ 465 | "" 466 | ] 467 | }, 468 | "metadata": {}, 469 | "output_type": "display_data" 470 | } 471 | ], 472 | "source": [ 473 | "from IPython.display import display, HTML\n", 474 | "def make_console_link(region, endpoint_name, task='[SageMaker LLM Serving]'):\n", 475 | " endpoint_link = f' {task} Check Endpoint Status' \n", 476 | " return endpoint_link\n", 477 | "\n", 478 | "endpoint_link = make_console_link(region, endpoint_name)\n", 479 | "display(HTML(endpoint_link))" 480 | ] 481 | }, 482 | { 483 | "cell_type": "code", 484 | "execution_count": 13, 485 | "id": "9d43b855-4d79-4460-aecc-6128356214da", 486 | "metadata": { 487 | "tags": [] 488 | }, 489 | "outputs": [ 490 | { 491 | "name": "stdout", 492 | "output_type": "stream", 493 | "text": [ 494 | "Endpoint is Creating\n", 495 | "Endpoint is Creating\n", 496 | "Endpoint is Creating\n", 497 | "Endpoint is Creating\n", 498 | "Endpoint is Creating\n", 499 | "Endpoint is Creating\n", 500 | "Endpoint is Creating\n", 501 | "Endpoint is Creating\n", 502 | "Endpoint is Creating\n", 503 | "Endpoint is Creating\n", 504 | "Endpoint is Creating\n", 505 | "Endpoint is Creating\n", 506 | "Endpoint is Creating\n", 507 | "Endpoint is Creating\n", 508 | "Endpoint is Creating\n", 509 | "Endpoint is InService\n", 510 | "CPU times: user 307 ms, sys: 10.1 ms, total: 317 ms\n", 511 | "Wall time: 15min 2s\n" 512 | ] 513 | } 514 | ], 515 | "source": [ 516 | "%%time \n", 517 | "from inference_lib import describe_endpoint, Prompter\n", 518 | "describe_endpoint(endpoint_name) " 519 | ] 520 | }, 521 | { 522 | "cell_type": "markdown", 523 | "id": "79b26c2e-b620-4df9-b712-c5aeb8e9e32a", 524 | "metadata": {}, 525 | "source": [ 526 | "
\n", 527 | "\n", 528 | "## 4. Inference\n", 529 | "---" 530 | ] 531 | }, 532 | { 533 | "cell_type": "markdown", 534 | "id": "cd435259-7952-4a76-b4ee-3360da5dd7c5", 535 | "metadata": {}, 536 | "source": [ 537 | "엔드포인트를 호출할 때 이 텍스트를 JSON 페이로드 내에 제공해야 합니다. 이 JSON 페이로드에는 length, sampling strategy, output token sequence restrictions을 제어하는 데 도움이 되는 원하는 추론 매개변수가 포함될 수 있습니다. 허깅페이스 트랜스포머 transformers 라이브러리에는 [사용 가능한 페이로드 매개변수](https://huggingface.co/docs/transformers/main_classes/text_generation#transformers.GenerationConfig)의 전체 목록이 정의되어 있지만, 중요한 페이로드 매개변수는 다음과 같이 정의되어 있습니다:\n", 538 | "\n", 539 | "* **do_sample (`bool`)** – logits sampling 활성화\n", 540 | "* **max_new_tokens (`int`)** – 생성된 토큰의 최대 수\n", 541 | "* **best_of (`int`)** – best_of 개의 시퀀스를 생성하고 가장 높은 토큰 로그 확률이 있는 경우 반환\n", 542 | "* **repetition_penalty (`float`)** – 반복 패널티에 대한 파라미터, 1.0은 패널티가 없음을 의미하여 Greedy 서치와 동일, 커질수록 다양한 결과를 얻을 수 있으며, 자세한 사항은 [this paper](https://arxiv.org/pdf/1909.05858.pdf)을 참고\n", 543 | "* **return_full_text (`bool`)** – 생성된 텍스트에 프롬프트를 추가할지 여부\n", 544 | "* **seed (`int`)** – Random sampling seed\n", 545 | "* **stop_sequences (`List[str]`)** – `stop_sequences` 가 생성되면 토큰 생성을 중지\n", 546 | "* **temperature (`float`)** – logits 분포 모듈화에 사용되는 값\n", 547 | "* **top_k (`int`)** – 상위 K개 만큼 가장 높은 확률 어휘 토큰의 수\n", 548 | "* **top_p (`float`)** – 1 보다 작게 설정하게 되며, 상위부터 정렬했을 때 가능한 토큰들의 확률을 합산하여 `top_p` 이상의 가장 작은 집합을 유지\n", 549 | "* **truncate (`int`)** – 입력 토큰을 지정된 크기로 잘라냄\n", 550 | "* **typical_p (`float`)** – typical Decoding 양으로, 자세한 사항은 [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666)을 참고\n", 551 | "* **watermark (`bool`)** – [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)가 Watermarking\n", 552 | "* **decoder_input_details (`bool`)** – decoder input token logprobs와 ids를 반환" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": null, 558 | "id": "12185205-fc49-4716-aacd-c1017a8541a0", 559 | "metadata": { 560 | "tags": [] 561 | }, 562 | "outputs": [], 563 | "source": [ 564 | "params = {\n", 565 | " \"do_sample\": False,\n", 566 | " \"max_new_tokens\": 128,\n", 567 | " \"temperature\": 0.4,\n", 568 | " \"top_p\": 0.9,\n", 569 | " \"return_full_text\": False,\n", 570 | " \"repetition_penalty\": 1.1,\n", 571 | " \"presence_penalty\": None,\n", 572 | " \"eos_token_id\": 2,\n", 573 | "}" 574 | ] 575 | }, 576 | { 577 | "cell_type": "code", 578 | "execution_count": 14, 579 | "id": "922d07d1-7bc6-457d-b18e-9bb4831e4f02", 580 | "metadata": { 581 | "tags": [] 582 | }, 583 | "outputs": [], 584 | "source": [ 585 | "import json\n", 586 | "from inference_utils import KoLLMSageMakerEndpoint\n", 587 | "pred = KoLLMSageMakerEndpoint(endpoint_name)" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": null, 593 | "id": "e4c4579f-7ef7-40d5-a943-ae8dc137b8bd", 594 | "metadata": { 595 | "tags": [] 596 | }, 597 | "outputs": [], 598 | "source": [ 599 | "instruction = \"다음 글을 요약해 주세요.\"\n", 600 | "context = \"\"\"\n", 601 | "엔터프라이즈 환경에서 생성 AI와 대규모 언어 모델(LLM; Large Language Models)의 가장 일반적인 유스케이스 중 하나는 기업의 지식 코퍼스를 기반으로 질문에 답변하는 것입니다. Amazon Lex는 AI 기반 챗봇을 구축하기 위한 프레임워크를 제공합니다. 사전 훈련된 파운데이션 모델(FM; Foundation Models)은 다양한 주제에 대한 요약, 텍스트 생성, 질문 답변과 같은 자연어 이해(NLU; Natural Language Understanding) 작업은 잘 수행하지만, 훈련 데이터의 일부로 보지 못한 콘텐츠에 대한 질문에는 정확한(오답 없이) 답변을 제공하는 데 어려움을 겪거나 완전히 실패합니다. 또한 FM은 특정 시점의 데이터 스냅샷으로 훈련하기에 추론 시점에 새로운 데이터에 액세스할 수 있는 고유한 기능이 없기에 잠재적으로 부정확하거나 부적절한 답변을 제공할 수 있습니다.\n", 602 | "\n", 603 | "이 문제를 해결하기 위해 흔히 사용되는 접근 방식은 검색 증강 생성(RAG; Retrieval Augmented Generation)이라는 기법을 사용하는 것입니다. RAG 기반 접근 방식에서는 LLM을 사용하여 사용자 질문을 벡터 임베딩으로 변환한 다음, 엔터프라이즈 지식 코퍼스에 대한 임베딩이 미리 채워진 벡터 데이터베이스에서 이러한 임베딩에 대한 유사성 검색을 수행합니다. 소수의 유사한 문서(일반적으로 3개)가 사용자 질문과 함께 다른 LLM에 제공된 ‘프롬프트’에 컨텍스트로 추가되고, 해당 LLM은 프롬프트에 컨텍스트로 제공된 정보를 사용하여 사용자 질문에 대한 답변을 생성합니다. RAG 모델은 매개변수 메모리(parametric memory)는 사전 훈련된 seq2seq 모델이고 비매개변수 메모리(non-parametric memory)는 사전 훈련된 신경망 검색기로 액세스되는 위키백과의 고밀도 벡터 색인 모델로 2020년에 Lewis 등이 도입했습니다. RAG 기반 접근 방식의 전반적 구조를 이해하려면 Question answering using Retrieval Augmented Generation with foundation models in Amazon SageMaker JumpStart 블로그를 참조하기 바랍니다.\n", 604 | "\"\"\"\n", 605 | "payload = pred.get_payload(instruction, context, params)" 606 | ] 607 | }, 608 | { 609 | "cell_type": "code", 610 | "execution_count": null, 611 | "id": "62f94f9b-9b69-408d-bec5-68a195879b00", 612 | "metadata": {}, 613 | "outputs": [], 614 | "source": [ 615 | "%%time\n", 616 | "generated_text = pred.infer(payload, verbose=True)" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": null, 622 | "id": "6d6bcdb9-b73d-468f-8606-6bf4b2f90a56", 623 | "metadata": {}, 624 | "outputs": [], 625 | "source": [ 626 | "endpoint_name_text = endpoint_name\n", 627 | "%store endpoint_name_text" 628 | ] 629 | }, 630 | { 631 | "cell_type": "markdown", 632 | "id": "625410bd-e2e9-4d57-bb35-bddf5cf20301", 633 | "metadata": {}, 634 | "source": [ 635 | "
\n", 636 | "\n", 637 | "## 5. Clean Up\n", 638 | "---" 639 | ] 640 | }, 641 | { 642 | "cell_type": "code", 643 | "execution_count": null, 644 | "id": "0b5d242e-3b9f-42e4-9ea0-c9596cae540d", 645 | "metadata": {}, 646 | "outputs": [], 647 | "source": [ 648 | "!rm -rf src" 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": null, 654 | "id": "44680c92-0623-46a0-9d3a-2efa262f9af6", 655 | "metadata": { 656 | "tags": [] 657 | }, 658 | "outputs": [], 659 | "source": [ 660 | "# - Delete the end point\n", 661 | "sm_client.delete_endpoint(EndpointName=endpoint_name)\n", 662 | "# - In case the end point failed we still want to delete the model\n", 663 | "sm_client.delete_endpoint_config(EndpointConfigName=endpoint_config_name)\n", 664 | "sm_client.delete_model(ModelName=model_name)" 665 | ] 666 | }, 667 | { 668 | "cell_type": "markdown", 669 | "id": "7feeb821-db0a-4a48-8550-b0146705b8d5", 670 | "metadata": { 671 | "tags": [] 672 | }, 673 | "source": [ 674 | "
\n", 675 | "\n", 676 | "# References\n", 677 | "---\n", 678 | "\n", 679 | "- Model 정보\n", 680 | " - kullm-polyglot-5.8b-v2\n", 681 | " - This model is a parameter-efficient fine-tuned version of EleutherAI/polyglot-ko-5.8b on a KULLM v2\n", 682 | " - https://huggingface.co/nlpai-lab/kullm-polyglot-5.8b-v2 \n", 683 | " - kullm-polyglot-12.8b-v2\n", 684 | " - This model is a fine-tuned version of EleutherAI/polyglot-ko-12.8b on a KULLM v2\n", 685 | " - https://huggingface.co/nlpai-lab/kullm-polyglot-12.8b-v2\n", 686 | " - beomi/KoAlpaca-Polyglot-12.8B\n", 687 | " - This model is a fine-tuned version of EleutherAI/polyglot-ko-12.8b on a KoAlpaca Dataset v1.1b\n", 688 | " - https://huggingface.co/beomi/KoAlpaca-Polyglot-12.8B\n", 689 | " - EleutherAI/polyglot-ko-12.8b\n", 690 | " - Polyglot-Ko-12.8B was trained for 167 billion tokens over 301,000 steps on 256 A100 GPUs with the GPT-NeoX framework. It was trained as an autoregressive language model, using cross-entropy loss to maximize the likelihood of predicting the next token.\n", 691 | " - License: Apache 2.0\n", 692 | " - https://huggingface.co/EleutherAI/polyglot-ko-12.8b \n", 693 | "- 코드\n", 694 | " - [Boto3](https://github.com/aws/amazon-sagemaker-examples/blob/main/advanced_functionality/pytorch_deploy_large_GPT_model/GPT-J-6B-model-parallel-inference-DJL.ipynb)\n", 695 | " - [Python SDK](https://github.com/aws/amazon-sagemaker-examples/blob/main/inference/generativeai/deepspeed/GPT-J-6B_DJLServing_with_PySDK.ipynb)\n", 696 | " - [Kor LLM on SageMaker](https://github.com/gonsoomoon-ml/Kor-LLM-On-SageMaker)\n", 697 | " - [AWS Generative AI Workshop for Korean language](https://github.com/aws-samples/aws-ai-ml-workshop-kr/tree/master/genai)" 698 | ] 699 | } 700 | ], 701 | "metadata": { 702 | "kernelspec": { 703 | "display_name": "conda_pytorch_p310", 704 | "language": "python", 705 | "name": "conda_pytorch_p310" 706 | }, 707 | "language_info": { 708 | "codemirror_mode": { 709 | "name": "ipython", 710 | "version": 3 711 | }, 712 | "file_extension": ".py", 713 | "mimetype": "text/x-python", 714 | "name": "python", 715 | "nbconvert_exporter": "python", 716 | "pygments_lexer": "ipython3", 717 | "version": "3.10.10" 718 | } 719 | }, 720 | "nbformat": 4, 721 | "nbformat_minor": 5 722 | } 723 | -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/TASK-5_OpenSearch_LLM_RAG_Streamlit_Chatbot_Example.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import sys 3 | import json 4 | import boto3 5 | import numpy as np 6 | from typing import Any, Dict, List, Optional 7 | from langchain.embeddings import SagemakerEndpointEmbeddings 8 | from langchain.llms.sagemaker_endpoint import LLMContentHandler, SagemakerEndpoint 9 | from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler 10 | from langchain import PromptTemplate 11 | from langchain.chains.question_answering import load_qa_chain 12 | from streamlit_chat import message 13 | from langchain.indexes import VectorstoreIndexCreator 14 | from langchain.vectorstores import Chroma, AtlasDB, FAISS 15 | from langchain.document_loaders.csv_loader import CSVLoader 16 | from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter 17 | import csv 18 | from langchain.vectorstores import OpenSearchVectorSearch 19 | import os 20 | import copy 21 | 22 | import sys 23 | sys.path.append('../utils') # src 폴더 경로 설정 24 | from streamlit_util import KoSimCSERobertaContentHandler, KullmContentHandler, SagemakerEndpointEmbeddingsJumpStart, KoSimCSERobertaContentHandler 25 | 26 | ########################################################################################################################################################################## 27 | # pip install -r ./requirements.txt in the system terminal 28 | # 29 | # 30 | # Case 1: SageMaker notebook Instance 31 | # notebook Instance의 Stramlit URL은 lab이하의 정보가 proxy/8501/로 대체됩니다. 32 | ## uri : https://ori-cuda-version.notebook.us-east-1.sagemaker.aws/lab/tree/main.ipynb 33 | ## streamlit : https://ori-cuda-version.notebook.us-east-1.sagemaker.aws/proxy/8501/ 34 | # 35 | # Case 2: SageMaker Studio lab 36 | # Studio의 Stramlit URL은 domain의 lab이하의 정보가 proxy/8501/webapp로 대체됩니다. 37 | ## uri > https://d-l2kk7xvxmnbl.studio.us-east-1.sagemaker.aws/jupyter/default/lab? 38 | ## streamlit : https://d-l2kk7xvxmnbl.studio.us-east-1.sagemaker.aws/jupyter/default/proxy/8501/webapp로 39 | # 참고 : https://aws.amazon.com/ko/blogs/tech/build-a-powerful-question-answering-bot-with-amazon-sagemaker-amazon-opensearch-service-streamlit-and-langchain/ 40 | ######################################################################################################################################################################### 41 | 42 | 43 | ######## AWS Setting 44 | aws_region = 'us-east-1' 45 | region ='us-east-1' 46 | service ='es' 47 | 48 | ######## For SageMaker 49 | # LLM Endpoint Name : 50 | llm_endpoint_name = 'kullm-polyglot-5-8b-v2-2023-08-23-15-47-39-450-endpoint' 51 | # Embedding Vector Model Endpoint Name : 52 | embvec_endpoint_name= 'KoSimCSE-roberta-2023-08-23-14-07-12' 53 | 54 | ######## For OpenSearch 55 | # Opensearch index name : 56 | index_name = 'fsi-sample' 57 | # Opensearch domain_endpoin name : 58 | opensearch_domain_endpoint = "https://search-ragopensearch-2pz3fgitugmvrz7vbngitqljzu.us-east-1.es.amazonaws.com" 59 | # Opensearch master user auth 60 | username = 'raguser' 61 | password = 'MarsEarth1!' 62 | 63 | #aws_access_key = os.environ['AWS_ACCESS_KEY'] 64 | #aws_secret_key =os.environ['AWS_SECRET_KEY'] 65 | ########################################################################################################################################################################## 66 | # 검색 rank 개수 67 | faiss_k =3 68 | 69 | # Kullum LLM 파라미터 설정 70 | params = { 71 | 'do_sample': False, 72 | 'max_new_tokens': 512, #128 73 | 'temperature': 1.0, # 0.5 ~ 1.0 default = 1.0 높으면 랜덤하게 자유도. 다음 생성 문장 토큰의 자유도 74 | 'top_k': 0, 75 | 'top_p': 0.9, 76 | 'return_full_text': False, 77 | 'repetition_penalty': 1.1, 78 | 'presence_penalty': None, 79 | 'eos_token_id': 2 80 | } 81 | ########################################################################################################################################################################## 82 | 83 | 84 | def load_chain(llm_endpoint_name): 85 | # KULLUM LLM 로드 86 | LLMTextContentHandler = KullmContentHandler() 87 | endpoint_name_text = llm_endpoint_name 88 | seperator = "||SPEPERATOR||" 89 | 90 | llm_text = SagemakerEndpoint( 91 | endpoint_name=endpoint_name_text, 92 | region_name=aws_region, 93 | model_kwargs=params, 94 | content_handler=LLMTextContentHandler, 95 | ) 96 | prompt_template = ''.join(["{context}", seperator, "{question}"]) 97 | 98 | PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"]) 99 | chain = load_qa_chain(llm=llm_text, chain_type="stuff", prompt=PROMPT, verbose=True) 100 | return chain 101 | 102 | ################################################################################################## 103 | # FAISS VectorStore - OpenSearch 104 | ################################################################################################## 105 | def load_emb_vec(embvec_endpoint_name): 106 | LLMEmbHandler = KoSimCSERobertaContentHandler() 107 | emb_vec = SagemakerEndpointEmbeddingsJumpStart( 108 | endpoint_name=embvec_endpoint_name, 109 | region_name=aws_region, 110 | content_handler=LLMEmbHandler, 111 | ) 112 | return emb_vec 113 | 114 | # opensearch score seems like ranking 115 | def filter_and_remove_score_opensearch_vector_score(res, cutoff_score = 0.006, variance=0.95): 116 | # Get the lowest score 117 | highest_score = max(score for doc, score in res) 118 | print('highest_score : ', highest_score) 119 | # If the lowest score is over 200, return an empty list 120 | if highest_score < cutoff_score: 121 | return [] 122 | # Calculate the upper bound for scores 123 | lower_bound = highest_score * variance 124 | print('lower_bound : ', lower_bound) 125 | # Filter the list and remove the score 126 | res = [doc for doc, score in res if score >= lower_bound] 127 | 128 | return res 129 | 130 | 131 | def get_similiar_docs(query, k=5, fetch_k=300, score=True, bank="신한은행"): 132 | print("bank : ", bank) 133 | #query = f'{bank}, {query}' 134 | print("query : ",query) 135 | 136 | if score: 137 | pre_similar_doc = vectro_db.similarity_search_with_score( 138 | query, 139 | k=k, 140 | fetch_k=fetch_k, 141 | search_type="approximate_search", # approximate_search, script_scoring, painless_scripting 142 | space_type="l2", # "l2", "l1", "linf", "cosinesimil", "innerproduct", "hammingbit"; 143 | pre_filter={"bool": {"filter": {"term": {"text": bank}}}}, 144 | boolean_filter={"bool": {"filter": {"term": {"text": bank}}}} 145 | # filter=dict(source=bank) 146 | ) 147 | print('jhs : ', pre_similar_doc) 148 | pretty_print_documents(pre_similar_doc) 149 | similar_docs = filter_and_remove_score_opensearch_vector_score(pre_similar_doc) 150 | else: 151 | similar_docs = vectro_db.similarity_search( 152 | query, 153 | k=k, 154 | search_type="approximate_search", # approximate_search, script_scoring, painless_scripting 155 | space_type="12", # "l2", "l1", "linf", "cosinesimil", "innerproduct", "hammingbit"; 156 | pre_filter={"bool": {"filter": {"term": {"text": bank}}}}, 157 | boolean_filter={"bool": {"filter": {"term": {"text": bank}}}} 158 | 159 | ) 160 | similar_docs_copy = copy.deepcopy(similar_docs) 161 | 162 | # print('similar_docs_copy : \n', similar_docs_copy) 163 | 164 | return similar_docs_copy 165 | 166 | # 임베딩 벡터 로드 167 | emb_vec = load_emb_vec(embvec_endpoint_name) 168 | 169 | # LLM 로드 170 | chain = load_chain(llm_endpoint_name) 171 | 172 | http_auth = (username, password) # opensearch user 173 | 174 | #OpenSearch Vector Indexer 175 | 176 | vectro_db = OpenSearchVectorSearch( 177 | index_name=index_name, 178 | opensearch_url=opensearch_domain_endpoint, 179 | embedding_function=emb_vec, 180 | http_auth=http_auth, 181 | is_aoss = False, 182 | engine="faiss", 183 | space_type="12" 184 | ) 185 | 186 | ################################################################################################## 187 | def pretty_print_documents(response): 188 | for doc, score in response: 189 | print(f'\nScore: {score}') 190 | print(f'Document Number: {doc.metadata["row"]}') 191 | print(f'Source: {doc.metadata["source"]}') 192 | 193 | # Split the page content into lines 194 | lines = doc.page_content.split("\n") 195 | 196 | # Extract and print each piece of information if it exists 197 | for line in lines: 198 | split_line = line.split(": ") 199 | if len(split_line) > 1: 200 | print(f'{split_line[0]}: {split_line[1]}') 201 | 202 | print('-' * 50) 203 | 204 | 205 | def get_answer(query): 206 | k = 3 207 | search_query = query 208 | 209 | similar_docs = get_similiar_docs(search_query, k=k, bank='신한은행') 210 | 211 | llm_query = ''+query+' Category에 대한 Information을 찾아서 설명해주세요.' 212 | 213 | if not similar_docs: 214 | llm_query = query 215 | 216 | answer = chain.run(input_documents=similar_docs, question=llm_query) 217 | 218 | return answer 219 | 220 | 221 | 222 | ################################################################################################## 223 | # Streamlit UI 224 | # From here down is all the StreamLit UI. 225 | ################################################################################################## 226 | st.set_page_config(page_title="FSI RAG FAQ Demo vectorstore mode", page_icon="🦜", layout="wide") 227 | st.header("🦜 FSI RAG Demo - Opensearch vectorstore with LLM mode") 228 | 229 | def get_text(): 230 | input_text = st.text_input("You: ", "", key="input") 231 | return input_text 232 | 233 | # 사용자로부터 입력을 받습니다. 234 | # user_input = get_text() 235 | 236 | # if "generated" not in st.session_state: 237 | # st.session_state["generated"] = [] 238 | # 239 | # if "past" not in st.session_state: 240 | # st.session_state["past"] = [] 241 | # 242 | # # 사용자가 입력을 제공했는지 확인합니다. 243 | # if user_input: 244 | # output = get_answer(user_input) 245 | # print("OUTPUT : ", output) 246 | # st.session_state.past.append(user_input) 247 | # st.session_state.generated.append(output) 248 | # 249 | # 250 | # 251 | # 252 | # if st.session_state["generated"]: 253 | # 254 | # for i in range(len(st.session_state["generated"]) - 1, -1, -1): 255 | # message(st.session_state["generated"][i], key=str(i)) 256 | # message(st.session_state["past"][i], is_user=True, key=str(i) + "_user") 257 | 258 | 259 | 260 | from langchain.callbacks import StreamlitCallbackHandler 261 | if "messages" not in st.session_state: 262 | st.session_state["messages"] = [] 263 | 264 | for msg in st.session_state.messages: 265 | st.chat_message(msg["role"]).write(msg["content"]) 266 | 267 | if prompt := st.chat_input(placeholder="여기에 금융 FAQ 질문해주세요"): 268 | st.session_state.messages.append({"role": "user", "content": prompt}) 269 | st.chat_message("user").write(prompt) 270 | 271 | 272 | with st.chat_message("assistant"): 273 | st_cb = StreamlitCallbackHandler(st.container(), expand_new_thoughts=False) 274 | response = get_answer(prompt) 275 | st.session_state.messages.append({"role": "assistant", "content": response}) 276 | st.write(response) 277 | -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | streamlit 3 | langchain 4 | streamlit_chat 5 | opensearch-py -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/src/kullm-polyglot-5-8b-v2/model.py: -------------------------------------------------------------------------------- 1 | from djl_python import Input, Output 2 | import os 3 | import deepspeed 4 | import torch 5 | import logging 6 | from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer 7 | from transformers import GPTNeoXLayer 8 | 9 | predictor = None 10 | 11 | def get_model(properties): 12 | 13 | tp_degree = properties["tensor_parallel_degree"] 14 | model_location = properties["model_dir"] 15 | if "model_id" in properties: 16 | model_location = properties["model_id"] 17 | task = properties["task"] 18 | 19 | logging.info(f"Loading model in {model_location}") 20 | local_rank = int(os.getenv("LOCAL_RANK", "0")) 21 | 22 | tokenizer = AutoTokenizer.from_pretrained(model_location) 23 | 24 | model = AutoModelForCausalLM.from_pretrained( 25 | model_location, 26 | torch_dtype=torch.float16, 27 | low_cpu_mem_usage=True, 28 | ) 29 | 30 | model.requires_grad_(False) 31 | model.eval() 32 | 33 | ds_config = { 34 | "tensor_parallel": {"tp_size": tp_degree}, 35 | "dtype": model.dtype, 36 | "injection_policy": { 37 | GPTNeoXLayer:('attention.dense', 'mlp.dense_4h_to_h') 38 | } 39 | } 40 | logging.info(f"Starting DeepSpeed init with TP={tp_degree}") 41 | model = deepspeed.init_inference(model, ds_config) 42 | 43 | generator = pipeline( 44 | task=task, model=model, tokenizer=tokenizer, device=local_rank 45 | ) 46 | # https://huggingface.co/docs/hub/models-tasks 47 | return generator 48 | 49 | def handle(inputs: Input) -> None: 50 | """ 51 | inputs: Contains the configurations from serving.properties 52 | """ 53 | global predictor 54 | if not predictor: 55 | predictor = get_model(inputs.get_properties()) 56 | 57 | if inputs.is_empty(): 58 | # Model server makes an empty call to warmup the model on startup 59 | logging.info("is_empty") 60 | return None 61 | 62 | data = inputs.get_as_json() #inputs.get_as_string() 63 | logging.info("data:", data) 64 | 65 | input_prompt, params = data["inputs"], data["parameters"] 66 | result = predictor(input_prompt, **params) 67 | logging.info("result:", result) 68 | 69 | return Output().add_as_json(result) #Output().add(result) 70 | -------------------------------------------------------------------------------- /RAG-SageMaker/rag-fsi-data-workshop/src/kullm-polyglot-5-8b-v2/serving.properties: -------------------------------------------------------------------------------- 1 | 2 | engine=DeepSpeed 3 | 4 | # passing extra options to model.py or built-in handler 5 | job_queue_size=100 6 | batch_size=1 7 | max_batch_delay=1 8 | max_idle_time=60 9 | 10 | # Built-in entrypoint 11 | #option.entryPoint=djl_python.deepspeed 12 | 13 | # Hugging Face model id 14 | option.model_id=nlpai-lab/kullm-polyglot-5.8b-v2 15 | 16 | # defines custom environment variables 17 | #env=SERVING_NUMBER_OF_NETTY_THREADS=2 18 | 19 | # Allows to load DeepSpeed workers in parallel 20 | option.parallel_loading=true 21 | 22 | # specify tensor parallel degree (number of partitions) 23 | option.tensor_parallel_degree=1 24 | 25 | # specify per model timeout 26 | option.model_loading_timeout=600 27 | #option.predict_timeout=240 28 | 29 | # mark the model as failure after python process crashing 10 times 30 | retry_threshold=0 31 | 32 | option.task=text-generation -------------------------------------------------------------------------------- /RAG-SageMaker/utils/inference_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import boto3 4 | import os.path as osp 5 | from typing import Union 6 | import pprint 7 | """ 8 | A dedicated helper to manage templates and prompt building. 9 | """ 10 | 11 | class Prompter(object): 12 | 13 | __slots__ = ("template", "_verbose") 14 | 15 | def __init__(self, template_name: str = "", verbose: bool = False): 16 | self._verbose = verbose 17 | if not template_name: 18 | # Enforce the default here, so the constructor can be called with '' and will not break. 19 | template_name = "alpaca" 20 | #file_name = osp.join("templates", f"{template_name}.json") 21 | file_name = osp.join("../utils", f"{template_name}.json") 22 | if not osp.exists(file_name): 23 | raise ValueError(f"Can't read {file_name}") 24 | with open(file_name) as fp: 25 | self.template = json.load(fp) 26 | if self._verbose: 27 | print( 28 | f"Using prompt template {template_name}: {self.template['description']}" 29 | ) 30 | 31 | def generate_prompt( 32 | self, 33 | instruction: str, 34 | input: Union[None, str] = None, 35 | label: Union[None, str] = None, 36 | ) -> str: 37 | # returns the full prompt from instruction and optional input 38 | # if a label (=response, =output) is provided, it's also appended. 39 | if input: 40 | res = self.template["prompt_input"].format( 41 | instruction=instruction, input=input 42 | ) 43 | else: 44 | res = self.template["prompt_no_input"].format( 45 | instruction=instruction 46 | ) 47 | if label: 48 | res = f"{res}{label}" 49 | if self._verbose: 50 | print(res) 51 | return res 52 | 53 | def get_response(self, output: str) -> str: 54 | return output.split(self.template["response_split"])[1].strip() 55 | 56 | def invoke_inference(endpoint_name, prompt): 57 | 58 | client = boto3.client("sagemaker-runtime") 59 | content_type = "application/json" 60 | 61 | response = client.invoke_endpoint( 62 | EndpointName=endpoint_name, 63 | ContentType=content_type, 64 | Body=json.dumps(prompt) 65 | ) 66 | res = response["Body"].read().decode() 67 | 68 | return res 69 | 70 | def parse_response(query_response): 71 | 72 | def traverse(o, tree_types=(list, tuple)): 73 | if isinstance(o, tree_types): 74 | for value in o: 75 | for subvalue in traverse(value, tree_types): 76 | yield subvalue 77 | else: 78 | yield o 79 | 80 | data = eval(query_response) 81 | 82 | listRes = [] 83 | for value in traverse(data): 84 | listRes.append(value["generated_text"]) 85 | 86 | if len(listRes) >= 2: return listRes 87 | else: return listRes[0].strip() 88 | 89 | 90 | 91 | class KoLLMSageMakerEndpoint(object): 92 | def __init__(self, endpoint_name): 93 | self.endpoint_name = endpoint_name 94 | self.prompter = Prompter("kullm") 95 | self.smr_client = boto3.client('sagemaker-runtime') 96 | 97 | def get_payload(self, instruction, input_text, params): 98 | prompter = Prompter("kullm") 99 | prompt = prompter.generate_prompt(instruction, input_text) 100 | payload = { 101 | 'inputs': prompt, 102 | 'parameters': params 103 | } 104 | return payload 105 | 106 | def infer(self, payload, verbose=True): 107 | 108 | content_type = "application/json" 109 | response = self.smr_client.invoke_endpoint( 110 | EndpointName=self.endpoint_name, 111 | ContentType=content_type, 112 | Body=json.dumps(payload) 113 | ) 114 | 115 | #model_predictions = json.loads(response['Body'].read().decode()) 116 | #s = model_predictions[0]['generated_text'] 117 | #generated_text = self.prompter.get_response(s) 118 | res = response["Body"].read().decode() 119 | generated_text = parse_response(res) 120 | generated_text = generated_text.split('###')[0] 121 | if verbose: 122 | pprint.pprint(f'Response: {generated_text}') 123 | return generated_text 124 | -------------------------------------------------------------------------------- /RAG-SageMaker/utils/kullm.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Alpaca-LoRA에서 사용하는 템플릿입니다.", 3 | "prompt_input": "아래는 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력이 짝을 이루는 예제입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 입력:\n{input}\n\n### 응답:\n", 4 | "prompt_no_input": "아래는 작업을 설명하는 명령어입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 응답:\n", 5 | "response_split": "### 응답:" 6 | } -------------------------------------------------------------------------------- /RAG-SageMaker/utils/streamlit_util.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import numpy as np 4 | from inference_utils import Prompter 5 | from typing import Any, Dict, List, Optional 6 | from langchain.embeddings import SagemakerEndpointEmbeddings 7 | from langchain.llms.sagemaker_endpoint import LLMContentHandler, SagemakerEndpoint 8 | from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler 9 | 10 | prompter = Prompter("kullm") 11 | 12 | class KullmContentHandler(LLMContentHandler): 13 | content_type = "application/json" 14 | accepts = "application/json" 15 | 16 | def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 17 | ''' 18 | 입력 데이터 전처리 후에 리턴 19 | ''' 20 | context, question = prompt.split("||SPEPERATOR||") 21 | prompt = prompter.generate_prompt(question, context) 22 | 23 | # print ("prompt", prompt) 24 | payload = { 25 | 'inputs': [prompt], 26 | 'parameters': model_kwargs 27 | } 28 | 29 | input_str = json.dumps(payload) 30 | 31 | return input_str.encode('utf-8') 32 | 33 | def transform_output(self, output: bytes) -> str: 34 | response_json = json.loads(output.read().decode("utf-8")) 35 | generated_text = response_json[0][0]["generated_text"] 36 | 37 | return generated_text 38 | 39 | 40 | class SagemakerEndpointEmbeddingsJumpStart(SagemakerEndpointEmbeddings): 41 | def embed_documents(self, texts: List[str], chunk_size: int = 1) -> List[List[float]]: 42 | """Compute doc embeddings using a SageMaker Inference Endpoint. 43 | 44 | Args: 45 | texts: The list of texts to embed. 46 | chunk_size: The chunk size defines how many input texts will 47 | be grouped together as request. If None, will use the 48 | chunk size specified by the class. 49 | 50 | Returns: 51 | List of embeddings, one for each text. 52 | """ 53 | results = [] 54 | _chunk_size = len(texts) if chunk_size > len(texts) else chunk_size 55 | 56 | print("text size: ", len(texts)) 57 | print("_chunk_size: ", _chunk_size) 58 | 59 | for i in range(0, len(texts), _chunk_size): 60 | # print (i, texts[i : i + _chunk_size]) 61 | response = self._embedding_func(texts[i: i + _chunk_size]) 62 | # print (i, response, len(response[0].shape)) 63 | 64 | results.extend(response) 65 | return results 66 | 67 | 68 | class KoSimCSERobertaContentHandler(EmbeddingsContentHandler): 69 | content_type = "application/json" 70 | accepts = "application/json" 71 | 72 | def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 73 | 74 | input_str = json.dumps({"inputs": prompt, **model_kwargs}) 75 | 76 | return input_str.encode("utf-8") 77 | 78 | def transform_output(self, output: bytes) -> str: 79 | 80 | response_json = json.loads(output.read().decode("utf-8")) 81 | ndim = np.array(response_json).ndim 82 | 83 | if ndim == 4: 84 | # Original shape (1, 1, n, 768) 85 | emb = response_json[0][0][0] 86 | emb = np.expand_dims(emb, axis=0).tolist() 87 | elif ndim == 2: 88 | # Original shape (n, 1) 89 | emb = [] 90 | for ele in response_json: 91 | e = ele[0][0] 92 | emb.append(e) 93 | else: 94 | print(f"Other # of dimension: {ndim}") 95 | emb = None 96 | return emb 97 | 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS-LLM-SageMaker 2 | 3 | 개발자와 솔루션 빌더를 대상으로 하는 이 실습 워크샵에서는 [Amazon SageMaker](https://aws.amazon.com/sagemaker/)을 통해 파운데이션 모델(FM)을 활용하는 방법을 소개합니다. 4 | 5 | 이 실습에서는 Generative AI에 대해 고객들이 가장 많이 사용하는 몇 가지 사용 패턴 GenAI를 이용하여 생산성을 향상시킴으로써 조직의 가치를 창출하는 기술의 예제를 보여줍니다. 6 | 이는 이메일 작성, 텍스트 요약, 질문에 대한 답변, 챗봇 구축, 이미지 생성에 도움이 되는 기초 모델을 활용하여 달성할 수 있습니다. 7 | 8 | 9 | ### AWS Samples Github 배포 실습 자료 안내 10 | 11 | 본 실습 자료는 AWS Samples Github에 배포됩니다. 현재 실습 자료는 AWS Samples 공식 자료보다 항상 최신 선반영됩니다. 12 | 13 | #### LLM - RAG: Opensearch with SageMaker Endpoint LLM Polyglot 14 | - 토픽: LLM - RAG: Opensearch with SageMaker Endpoint LLM Polyglot 15 | - 반영 링크: https://github.com/aws-samples/aws-ai-ml-workshop-kr/tree/master/genai/aws-gen-ai-kr/20_applications/04_rag_finance_opensearch_sllm_workshop 16 | - 최근 반영일: 2024.04.25 17 | 18 | #### [Tuner] QLoRA fine-tuning 19 | - 토픽: [Tuner] QLoRA fine-tuning 20 | - 반영 링크: https://github.com/aws-samples/aws-ai-ml-workshop-kr/tree/master/genai/aws-gen-ai-kr/30_fine_tune/01-instruction-tuning-peft-qlora 21 | - 최근 반영일: 2024.04.18 22 | 23 | ## LLM - RAG : Opensearch with SageMaker Endpoint LLM Ployglot 24 | 1. [Amazon SageMaker와 Amazon Opensearch로 RAG (Retrieval-Augmented Generation) 구현실습 ](https://github.com/hyeonsangjeon/AWS-LLM-SageMaker/tree/main/RAG-SageMaker/rag-fsi-data-workshop) - 25 | RAG (Retrieval-Augmented Generation)는 정보 검색과 텍스트 생성을 결합한 혁신적인 NLP 아키텍처입니다. 이번 실습에서는 RAG가 어떻게 Amazon Opensearch와 통합되어 외부의 신뢰할 수 있는 데이터베이스나 문서를 검색하는 과정을 강화하는지 간단한 실습을 통해 알아봅니다. 26 | 이 실습에서는 SageMaker Endpoint와 Amazon Openssearch에서 Embedding 데이터 입력, SDK, 그리고 [LangChain](https://python.langchain.com/docs/get_started/introduction) 및 [FAISS](https://faiss.ai/index.html)와 같은 오픈소스 소프트웨어를 통해 이러한 패턴을 구현하는 실무 경험을 쌓을 수 있습니다. 27 | 28 | 29 | #### SageMaker Training deep-learning-containers 30 | - deep-learning Base Container image link : https://github.com/aws/deep-learning-containers/blob/master/available_images.md 31 | 32 | ## [Tuner] QLoRA fine-tuning 33 | - [KULLM-Polyglot-12.8B](PEFT) 34 | 35 | ### Filenames 36 | - `1_prepare-dataset-alpaca-method.ipynb`: instruction 데이터 세트로부터 훈련 데이터 세트를 준비합니다. 각 샘플을 토크나이즈하는 방식입니다. 37 | - `1_prepare-dataset-chunk-method.ipynb`: instruction 데이터 세트로부터 훈련 데이터 세트를 준비합니다. 샘플을 모두 모아서(concatenate) 청크 크기(chunk size)만큼 분할하는 방식입니다. 38 | - `2_local-train-debug-lora.ipynb`: 본격적으로 훈련 인스턴스에서 수행하기 전에 개발 환경에서 일부 샘플 데이터로 디버그를 수행합니다. 이미 파인 튜닝에 익숙한 분들은 이 핸즈온을 건너뛰고 3_sm-train-lora.ipynb을 진행해 주세요. 39 | - `3_sm-train-lora.ipynb`: SageMaker 훈련 인스턴스에서 파인튜닝을 수행합니다. 40 | 41 | -------------------------------------------------------------------------------- /common_code/inference_lib.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import time 3 | import json 4 | 5 | 6 | """ 7 | A dedicated helper to manage templates and prompt building. 8 | """ 9 | 10 | import json 11 | import os.path as osp 12 | from typing import Union 13 | import os 14 | import pathlib 15 | 16 | class Prompter(object): 17 | __slots__ = ("template", "_verbose") 18 | 19 | def __init__(self, template_name: str = "", verbose: bool = False): 20 | self._verbose = verbose 21 | if not template_name: 22 | # Enforce the default here, so the constructor can be called with '' and will not break. 23 | template_name = "alpaca" 24 | #file_name = osp.join("templates", f"{template_name}.json") 25 | # file_name = str(pathlib.Path().home()) + '/Kor-LLM-On-SageMaker/common_code' # + f"{template_name}.json" 26 | file_name = osp.join("/root/Kor-LLM-On-SageMaker/common_code", f"{template_name}.json") 27 | 28 | 29 | 30 | # path = pathlib.Path.cwd() 31 | # print("Pathlib: ", path) 32 | 33 | # print(pathlib.Path().home()) 34 | 35 | # path = pathlib.Path().home() + '/Kor-LLM-On-SageMaker/common_code' 36 | 37 | 38 | # print("pwd: ", os.getcwd()) 39 | # file_name = f"{template_name}.json" 40 | if not osp.exists(file_name): 41 | raise ValueError(f"Can't read {file_name}") 42 | with open(file_name) as fp: 43 | self.template = json.load(fp) 44 | if self._verbose: 45 | print( 46 | f"Using prompt template {template_name}: {self.template['description']}" 47 | ) 48 | 49 | def generate_prompt( 50 | self, 51 | instruction: str, 52 | input: Union[None, str] = None, 53 | label: Union[None, str] = None, 54 | ) -> str: 55 | # returns the full prompt from instruction and optional input 56 | # if a label (=response, =output) is provided, it's also appended. 57 | if input: 58 | res = self.template["prompt_input"].format( 59 | instruction=instruction, input=input 60 | ) 61 | else: 62 | res = self.template["prompt_no_input"].format( 63 | instruction=instruction 64 | ) 65 | if label: 66 | res = f"{res}{label}" 67 | if self._verbose: 68 | print(res) 69 | return res 70 | 71 | def get_response(self, output: str) -> str: 72 | return output.split(self.template["response_split"])[1].strip() 73 | 74 | def describe_endpoint(endpoint_name): 75 | ''' 76 | 엔드폰인트 생성 유무를 확인. 생성 중이면 기다림. 77 | ''' 78 | sm_client = boto3.client("sagemaker") 79 | 80 | while(True): 81 | response = sm_client.describe_endpoint( 82 | EndpointName= endpoint_name 83 | ) 84 | status = response['EndpointStatus'] 85 | if status == 'Creating': 86 | print("Endpoint is ", status) 87 | time.sleep(60) 88 | else: 89 | print("Endpoint is ", status) 90 | break 91 | 92 | 93 | def invoke_inference(endpoint_name, prompt): 94 | ''' 95 | KoAlpaca 프롬프트를 제공하여 엔드포인트 호출 96 | ''' 97 | client = boto3.client("sagemaker-runtime") 98 | 99 | content_type = "text/plain" 100 | response = client.invoke_endpoint( 101 | EndpointName=endpoint_name, ContentType=content_type, Body=prompt 102 | ) 103 | #print(response["Body"].read()) 104 | res = response["Body"].read().decode() 105 | print (eval(res)[0]['generated_text']) 106 | 107 | def invoke_inference_DJ(endpoint_name, prompt): 108 | 109 | ''' 110 | invoke_inference 변형, 111 | 곤수님께서 기존에 invoke_inference를 사용하는 부분이 있어 우선 이름을 달리 함 112 | 추후 invoke_inference과 하나로 합칠 예정 113 | ''' 114 | 115 | ''' 116 | KoAlpaca 프롬프트를 제공하여 엔드포인트 호출 117 | ''' 118 | 119 | client = boto3.client("sagemaker-runtime") 120 | 121 | content_type = "application/json" 122 | response = client.invoke_endpoint( 123 | EndpointName=endpoint_name, 124 | ContentType=content_type, 125 | Body=json.dumps(prompt) 126 | ) 127 | 128 | res = response["Body"].read().decode() 129 | # print (res) 130 | 131 | return res 132 | 133 | def query_endpoint_with_text_payload(plain_text, endpoint_name, content_type="text/plain"): 134 | ''' 135 | content_type 이 text/plain 인 경우 사용 136 | ''' 137 | client = boto3.client("runtime.sagemaker") 138 | response = client.invoke_endpoint( 139 | EndpointName=endpoint_name, ContentType=content_type, Body=plain_text 140 | ) 141 | return response 142 | 143 | 144 | def parse_response_text_model(query_response): 145 | ''' 146 | content_type 이 text/plain 인 경우 사용 147 | ''' 148 | 149 | model_predictions = json.loads(query_response["Body"].read()) 150 | # print("model_predictions: \n", model_predictions) 151 | generated_text = model_predictions[0]["generated_text"] 152 | return generated_text 153 | 154 | def parse_response(query_response): 155 | 156 | def traverse(o, tree_types=(list, tuple)): 157 | if isinstance(o, tree_types): 158 | for value in o: 159 | for subvalue in traverse(value, tree_types): 160 | yield subvalue 161 | else: 162 | yield o 163 | 164 | data = eval(query_response) 165 | 166 | listRes = [] 167 | for value in traverse(data): 168 | listRes.append(value["generated_text"]) 169 | 170 | if len(listRes) >= 2: return listRes 171 | else: return listRes[0].strip() 172 | 173 | ################################################ 174 | # Embedding Handler 175 | ################################################ 176 | 177 | # from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler 178 | # from langchain.embeddings import SagemakerEndpointEmbeddings 179 | # from langchain.llms.sagemaker_endpoint import ContentHandlerBase 180 | # from typing import Any, Dict, List, Optional 181 | 182 | # class SagemakerEndpointEmbeddingsJumpStart(SagemakerEndpointEmbeddings): 183 | # def embed_documents(self, texts: List[str], chunk_size: int = 5) -> List[List[float]]: 184 | # """Compute doc embeddings using a SageMaker Inference Endpoint. 185 | 186 | # Args: 187 | # texts: The list of texts to embed. 188 | # chunk_size: The chunk size defines how many input texts will 189 | # be grouped together as request. If None, will use the 190 | # chunk size specified by the class. 191 | 192 | # Returns: 193 | # List of embeddings, one for each text. 194 | # """ 195 | # results = [] 196 | # _chunk_size = len(texts) if chunk_size > len(texts) else chunk_size 197 | 198 | # # print("text size: ", len(texts)) 199 | # # print("_chunk_size: ", _chunk_size) 200 | 201 | # for i in range(0, len(texts), _chunk_size): 202 | # response = self._embedding_func(texts[i : i + _chunk_size]) 203 | # print 204 | # results.extend(response) 205 | # return results 206 | 207 | # import numpy as np 208 | 209 | # class KoSimCSERobertaContentHandler(EmbeddingsContentHandler): 210 | # content_type = "application/json" 211 | # accepts = "application/json" 212 | 213 | # def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 214 | # input_str = json.dumps({"inputs": prompt, **model_kwargs}) 215 | # return input_str.encode("utf-8") 216 | 217 | # def transform_output(self, output: bytes) -> str: 218 | # response_json = json.loads(output.read().decode("utf-8")) 219 | # ndim = np.array(response_json).ndim 220 | # # print("response_json ndim: \n", ndim) 221 | # # print("response_json shape: \n", np.array(response_json).shape) 222 | # if ndim == 4: 223 | # # Original shape (1, 1, n, 768) 224 | # emb = response_json[0][0][0] 225 | # emb = np.expand_dims(emb, axis=0).tolist() 226 | # # print("emb shape: ", np.array(emb).shape) 227 | # # print("emb TYPE: ", type(emb)) 228 | # elif ndim == 2: 229 | # # Original shape (n, 1) 230 | # # print(response_json[0]) 231 | # emb = [] 232 | # for ele in response_json: 233 | # # print(np.array(response_json[0]).shape) 234 | # e = ele[0][0] 235 | # #emb = np.expand_dims(emb, axis=0).tolist() 236 | # # print("emb shape: ", np.array(emb).shape) 237 | # # print("emb TYPE: ", type(emb)) 238 | # emb.append(e) 239 | # # print("emb_list shape: ", np.array(emb).shape) 240 | # # print("emb_list TYPE: ", type(emb)) 241 | # else: 242 | # print(f"Other # of dimension: {ndim}") 243 | # emb = None 244 | # return emb 245 | 246 | 247 | # ################################################ 248 | # # LLM Handler 249 | # ################################################ 250 | # from langchain.llms.sagemaker_endpoint import LLMContentHandler 251 | # import json 252 | 253 | # class KoAlpacaContentHandler(LLMContentHandler): 254 | # content_type = "application/json" 255 | # accepts = "application/json" 256 | 257 | # def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 258 | # input_str = json.dumps({"text_inputs": prompt, **model_kwargs}) 259 | # return input_str.encode("utf-8") 260 | 261 | # def transform_output(self, output: bytes) -> str: 262 | # print("In KoAlpacaContentHandler") 263 | # # print("output: ", output) 264 | # response_json = json.loads(output.read().decode("utf-8")) 265 | # print("response_json: ", response_json) 266 | # # return response_json["generated_texts"][0] 267 | # doc = response_json[0]['generated_text'] 268 | # doc = json.loads(doc) 269 | # doc = doc['text_inputs'] 270 | # return doc 271 | -------------------------------------------------------------------------------- /common_code/kullm.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Alpaca-LoRA에서 사용하는 템플릿입니다.", 3 | "prompt_input": "아래는 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력이 짝을 이루는 예제입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 입력:\n{input}\n\n### 응답:\n", 4 | "prompt_no_input": "아래는 작업을 설명하는 명령어입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 응답:\n", 5 | "response_split": "### 응답:" 6 | } -------------------------------------------------------------------------------- /images/Nfloat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/Nfloat.png -------------------------------------------------------------------------------- /images/TensorShard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/TensorShard.png -------------------------------------------------------------------------------- /images/lora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/lora.png -------------------------------------------------------------------------------- /images/lora_eq1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/lora_eq1.png -------------------------------------------------------------------------------- /images/lora_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/lora_r.png -------------------------------------------------------------------------------- /images/qlora_eq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/qlora_eq.png -------------------------------------------------------------------------------- /images/qlora_fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/qlora_fig1.png -------------------------------------------------------------------------------- /images/quantization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/images/quantization.png -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # Prompt templates 2 | 3 | This directory contains template styles for the prompts used to finetune LoRA models. 4 | 5 | ## Format 6 | 7 | A template is described via a JSON file with the following keys: 8 | 9 | - `prompt_input`: The template to use when input is not None. Uses `{instruction}` and `{input}` placeholders. 10 | - `prompt_no_input`: The template to use when input is None. Uses `{instruction}` placeholders. 11 | - `description`: A short description of the template, with possible use cases. 12 | - `response_split`: The text to use as separator when cutting real response from the model output. 13 | 14 | No `{response}` placeholder was used, since the response is always the last element of the template and is just to be concatenated to the rest. 15 | 16 | ## Example template 17 | 18 | The default template, used unless otherwise specified, is `alpaca.json` 19 | 20 | ```json 21 | { 22 | "description": "Template used by Alpaca-LoRA.", 23 | "prompt_input": "Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n", 24 | "prompt_no_input": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:\n", 25 | "response_split": "### Response:" 26 | } 27 | 28 | ``` 29 | 30 | ## Current templates 31 | 32 | ### alpaca 33 | 34 | Default template used for generic LoRA fine tunes so far. 35 | 36 | ### alpaca_legacy 37 | 38 | Legacy template used by the original alpaca repo, with no `\n` after the response field. Kept for reference and experiments. 39 | 40 | ### alpaca_short 41 | 42 | A trimmed down alpaca template which seems to perform just as well and spare some tokens. Models created with the default template seem to be queryable by the short tempalte as well. More experiments are welcome. 43 | 44 | ### vigogne 45 | 46 | The default alpaca template, translated to french. This template was used to train the "Vigogne" LoRA and is to be used to query it, or for extra fine tuning. 47 | -------------------------------------------------------------------------------- /templates/alpaca.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Template used by Alpaca-LoRA.", 3 | "prompt_input": "Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n", 4 | "prompt_no_input": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:\n", 5 | "response_split": "### Response:" 6 | } 7 | -------------------------------------------------------------------------------- /templates/alpaca_legacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Legacy template, used by Original Alpaca repository.", 3 | "prompt_input": "Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:", 4 | "prompt_no_input": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\n{instruction}\n\n### Response:", 5 | "response_split": "### Response:" 6 | } 7 | -------------------------------------------------------------------------------- /templates/alpaca_short.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "A shorter template to experiment with.", 3 | "prompt_input": "### Instruction:\n{instruction}\n\n### Input:\n{input}\n\n### Response:\n", 4 | "prompt_no_input": "### Instruction:\n{instruction}\n\n### Response:\n", 5 | "response_split": "### Response:" 6 | } 7 | -------------------------------------------------------------------------------- /templates/korwkv.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt_input": "### 명령어:\n{instruction}\n\n### 질문:\n{input}\n\n### 답변:\n", 3 | "prompt_no_input": "### 명령어:\n{instruction}\n\n### 답변:\n", 4 | "response_split": "### 답변:" 5 | } -------------------------------------------------------------------------------- /templates/kullm.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Alpaca-LoRA에서 사용하는 템플릿입니다.", 3 | "prompt_input": "아래는 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력이 짝을 이루는 예제입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 입력:\n{input}\n\n### 응답:\n", 4 | "prompt_no_input": "아래는 작업을 설명하는 명령어입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 응답:\n", 5 | "response_split": "### 응답:" 6 | } 7 | -------------------------------------------------------------------------------- /templates/vigogne.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "French template, used by Vigogne for finetuning.", 3 | "prompt_input": "Ci-dessous se trouve une instruction qui décrit une tâche, associée à une entrée qui fournit un contexte supplémentaire. Écrivez une réponse qui complète correctement la demande.\n\n### Instruction:\n{instruction}\n\n### Entrée:\n{input}\n\n### Réponse:\n", 4 | "prompt_no_input": "Ci-dessous se trouve une instruction qui décrit une tâche. Écrivez une réponse qui complète correctement la demande.\n\n### Instruction:\n{instruction}\n\n### Réponse:\n", 5 | "response_split": "### Réponse:" 6 | } 7 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyeonsangjeon/AWS-LLM-SageMaker/6949934df9fa40cc8ff2afda07a3250b7a126f11/utils/__init__.py -------------------------------------------------------------------------------- /utils/callbacks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers to support streaming generate output. 3 | Borrowed from https://github.com/oobabooga/text-generation-webui/blob/ad37f396fc8bcbab90e11ecf17c56c97bfbd4a9c/modules/callbacks.py 4 | """ 5 | 6 | import gc 7 | import traceback 8 | from queue import Queue 9 | from threading import Thread 10 | 11 | import torch 12 | import transformers 13 | 14 | 15 | class Stream(transformers.StoppingCriteria): 16 | def __init__(self, callback_func=None): 17 | self.callback_func = callback_func 18 | 19 | def __call__(self, input_ids, scores) -> bool: 20 | if self.callback_func is not None: 21 | self.callback_func(input_ids[0]) 22 | return False 23 | 24 | 25 | class Iteratorize: 26 | 27 | """ 28 | Transforms a function that takes a callback 29 | into a lazy iterator (generator). 30 | """ 31 | 32 | def __init__(self, func, kwargs={}, callback=None): 33 | self.mfunc = func 34 | self.c_callback = callback 35 | self.q = Queue() 36 | self.sentinel = object() 37 | self.kwargs = kwargs 38 | self.stop_now = False 39 | 40 | def _callback(val): 41 | if self.stop_now: 42 | raise ValueError 43 | self.q.put(val) 44 | 45 | def gentask(): 46 | try: 47 | ret = self.mfunc(callback=_callback, **self.kwargs) 48 | except ValueError: 49 | pass 50 | except: 51 | traceback.print_exc() 52 | pass 53 | 54 | self.q.put(self.sentinel) 55 | if self.c_callback: 56 | self.c_callback(ret) 57 | 58 | self.thread = Thread(target=gentask) 59 | self.thread.start() 60 | 61 | def __iter__(self): 62 | return self 63 | 64 | def __next__(self): 65 | obj = self.q.get(True, None) 66 | if obj is self.sentinel: 67 | raise StopIteration 68 | else: 69 | return obj 70 | 71 | def __enter__(self): 72 | return self 73 | 74 | def __exit__(self, exc_type, exc_val, exc_tb): 75 | self.stop_now = True 76 | -------------------------------------------------------------------------------- /utils/common_lib.py: -------------------------------------------------------------------------------- 1 | def check_packages(): 2 | try: 3 | import langchain 4 | _has_packages = True 5 | except (ImportError, AttributeError): 6 | _has_packages = False 7 | 8 | if _has_packages: 9 | print("Proceed.") 10 | else: 11 | print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++") 12 | print("[ERROR] 0번 모듈 노트북(0_setup.ipynb)을 먼저 실행해 주세요.") 13 | print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++") -------------------------------------------------------------------------------- /utils/inference_lib.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import time 3 | import json 4 | import os.path as osp 5 | from typing import Union 6 | import pprint 7 | 8 | def parse_response(query_response): 9 | 10 | def traverse(o, tree_types=(list, tuple)): 11 | if isinstance(o, tree_types): 12 | for value in o: 13 | for subvalue in traverse(value, tree_types): 14 | yield subvalue 15 | else: 16 | yield o 17 | 18 | data = eval(query_response) 19 | 20 | listRes = [] 21 | for value in traverse(data): 22 | listRes.append(value["generated_text"]) 23 | 24 | if len(listRes) >= 2: return listRes 25 | else: return listRes[0].strip() 26 | 27 | # def invoke_inference(endpoint_name, prompt): 28 | # ''' 29 | # KoAlpaca 프롬프트를 제공하여 엔드포인트 호출 30 | # ''' 31 | # client = boto3.client("sagemaker-runtime") 32 | 33 | # content_type = "text/plain" 34 | # response = client.invoke_endpoint( 35 | # EndpointName=endpoint_name, ContentType=content_type, Body=prompt 36 | # ) 37 | # #print(response["Body"].read()) 38 | # res = response["Body"].read().decode() 39 | # print (eval(res)[0]['generated_text']) 40 | 41 | # def invoke_inference_DJ(endpoint_name, prompt): 42 | 43 | # client = boto3.client("sagemaker-runtime") 44 | 45 | # content_type = "application/json" 46 | # response = client.invoke_endpoint( 47 | # EndpointName=endpoint_name, 48 | # ContentType=content_type, 49 | # Body=json.dumps(prompt) 50 | # ) 51 | 52 | # res = response["Body"].read().decode() 53 | # return res 54 | 55 | # def query_endpoint_with_text_payload(plain_text, endpoint_name, content_type="text/plain"): 56 | # ''' 57 | # content_type 이 text/plain 인 경우 사용 58 | # ''' 59 | # client = boto3.client("runtime.sagemaker") 60 | # response = client.invoke_endpoint( 61 | # EndpointName=endpoint_name, ContentType=content_type, Body=plain_text 62 | # ) 63 | # return response 64 | 65 | 66 | # def parse_response_text_model(query_response): 67 | # ''' 68 | # content_type 이 text/plain 인 경우 사용 69 | # ''' 70 | 71 | # model_predictions = json.loads(query_response["Body"].read()) 72 | # # print("model_predictions: \n", model_predictions) 73 | # generated_text = model_predictions[0]["generated_text"] 74 | # return generated_text 75 | 76 | 77 | """ 78 | A dedicated helper to manage templates and prompt building. 79 | """ 80 | 81 | class Prompter(object): 82 | __slots__ = ("template", "_verbose") 83 | 84 | def __init__(self, template_name: str = "", verbose: bool = False): 85 | self._verbose = verbose 86 | if not template_name: 87 | # Enforce the default here, so the constructor can be called with '' and will not break. 88 | template_name = "alpaca" 89 | file_name = osp.join("../templates", f"{template_name}.json") 90 | if not osp.exists(file_name): 91 | raise ValueError(f"Can't read {file_name}") 92 | with open(file_name) as fp: 93 | self.template = json.load(fp) 94 | if self._verbose: 95 | print( 96 | f"Using prompt template {template_name}: {self.template['description']}" 97 | ) 98 | 99 | def generate_prompt( 100 | self, 101 | instruction: str, 102 | input: Union[None, str] = None, 103 | label: Union[None, str] = None, 104 | ) -> str: 105 | # returns the full prompt from instruction and optional input 106 | # if a label (=response, =output) is provided, it's also appended. 107 | if input: 108 | res = self.template["prompt_input"].format( 109 | instruction=instruction, input=input 110 | ) 111 | else: 112 | res = self.template["prompt_no_input"].format( 113 | instruction=instruction 114 | ) 115 | if label: 116 | res = f"{res}{label}" 117 | if self._verbose: 118 | print(res) 119 | return res 120 | 121 | def get_response(self, output: str) -> str: 122 | return output.split(self.template["response_split"])[1].strip() 123 | 124 | 125 | def describe_endpoint(endpoint_name): 126 | ''' 127 | 엔드폰인트 생성 유무를 확인. 생성 중이면 기다림. 128 | ''' 129 | sm_client = boto3.client("sagemaker") 130 | 131 | while(True): 132 | response = sm_client.describe_endpoint( 133 | EndpointName= endpoint_name 134 | ) 135 | status = response['EndpointStatus'] 136 | if status == 'Creating': 137 | print("Endpoint is ", status) 138 | time.sleep(60) 139 | else: 140 | print("Endpoint is ", status) 141 | break 142 | 143 | 144 | class KoLLMSageMakerEndpoint(object): 145 | def __init__(self, endpoint_name): 146 | self.endpoint_name = endpoint_name 147 | self.prompter = Prompter("kullm") 148 | self.smr_client = boto3.client('sagemaker-runtime') 149 | 150 | def get_payload(self, instruction, input_text, params): 151 | prompt = self.prompter.generate_prompt(instruction, input_text) 152 | payload = { 153 | 'inputs': prompt, 154 | 'parameters': params 155 | } 156 | payload_str = json.dumps(payload) 157 | return payload_str.encode("utf-8") 158 | 159 | def infer(self, payload, content_type="application/json", verbose=True): 160 | response = self.smr_client.invoke_endpoint( 161 | EndpointName=self.endpoint_name, 162 | ContentType=content_type, 163 | Body=payload 164 | ) 165 | 166 | res = json.loads(response['Body'].read().decode("utf-8")) 167 | generated_text = res[0]["generated_text"] 168 | #generated_text = self.prompter.get_response(generated_text) 169 | 170 | generated_text = generated_text.split('###')[0] 171 | if verbose: 172 | pprint.pprint(f'Response: {generated_text}') 173 | return generated_text 174 | 175 | 176 | ################################################ 177 | # Embedding Handler 178 | ################################################ 179 | 180 | # from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler 181 | # from langchain.embeddings import SagemakerEndpointEmbeddings 182 | # from langchain.llms.sagemaker_endpoint import ContentHandlerBase 183 | # from typing import Any, Dict, List, Optional 184 | 185 | # class SagemakerEndpointEmbeddingsJumpStart(SagemakerEndpointEmbeddings): 186 | # def embed_documents(self, texts: List[str], chunk_size: int = 5) -> List[List[float]]: 187 | # """Compute doc embeddings using a SageMaker Inference Endpoint. 188 | 189 | # Args: 190 | # texts: The list of texts to embed. 191 | # chunk_size: The chunk size defines how many input texts will 192 | # be grouped together as request. If None, will use the 193 | # chunk size specified by the class. 194 | 195 | # Returns: 196 | # List of embeddings, one for each text. 197 | # """ 198 | # results = [] 199 | # _chunk_size = len(texts) if chunk_size > len(texts) else chunk_size 200 | 201 | # # print("text size: ", len(texts)) 202 | # # print("_chunk_size: ", _chunk_size) 203 | 204 | # for i in range(0, len(texts), _chunk_size): 205 | # response = self._embedding_func(texts[i : i + _chunk_size]) 206 | # print 207 | # results.extend(response) 208 | # return results 209 | 210 | # import numpy as np 211 | 212 | # class KoSimCSERobertaContentHandler(EmbeddingsContentHandler): 213 | # content_type = "application/json" 214 | # accepts = "application/json" 215 | 216 | # def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 217 | # input_str = json.dumps({"inputs": prompt, **model_kwargs}) 218 | # return input_str.encode("utf-8") 219 | 220 | # def transform_output(self, output: bytes) -> str: 221 | # response_json = json.loads(output.read().decode("utf-8")) 222 | # ndim = np.array(response_json).ndim 223 | # # print("response_json ndim: \n", ndim) 224 | # # print("response_json shape: \n", np.array(response_json).shape) 225 | # if ndim == 4: 226 | # # Original shape (1, 1, n, 768) 227 | # emb = response_json[0][0][0] 228 | # emb = np.expand_dims(emb, axis=0).tolist() 229 | # # print("emb shape: ", np.array(emb).shape) 230 | # # print("emb TYPE: ", type(emb)) 231 | # elif ndim == 2: 232 | # # Original shape (n, 1) 233 | # # print(response_json[0]) 234 | # emb = [] 235 | # for ele in response_json: 236 | # # print(np.array(response_json[0]).shape) 237 | # e = ele[0][0] 238 | # #emb = np.expand_dims(emb, axis=0).tolist() 239 | # # print("emb shape: ", np.array(emb).shape) 240 | # # print("emb TYPE: ", type(emb)) 241 | # emb.append(e) 242 | # # print("emb_list shape: ", np.array(emb).shape) 243 | # # print("emb_list TYPE: ", type(emb)) 244 | # else: 245 | # print(f"Other # of dimension: {ndim}") 246 | # emb = None 247 | # return emb 248 | 249 | 250 | # ################################################ 251 | # # LLM Handler 252 | # ################################################ 253 | # from langchain.llms.sagemaker_endpoint import LLMContentHandler 254 | # import json 255 | 256 | # class KoAlpacaContentHandler(LLMContentHandler): 257 | # content_type = "application/json" 258 | # accepts = "application/json" 259 | 260 | # def transform_input(self, prompt: str, model_kwargs={}) -> bytes: 261 | # input_str = json.dumps({"text_inputs": prompt, **model_kwargs}) 262 | # return input_str.encode("utf-8") 263 | 264 | # def transform_output(self, output: bytes) -> str: 265 | # print("In KoAlpacaContentHandler") 266 | # # print("output: ", output) 267 | # response_json = json.loads(output.read().decode("utf-8")) 268 | # print("response_json: ", response_json) 269 | # # return response_json["generated_texts"][0] 270 | # doc = response_json[0]['generated_text'] 271 | # doc = json.loads(doc) 272 | # doc = doc['text_inputs'] 273 | # return doc --------------------------------------------------------------------------------