목차
SageMaker, Streamlit, Opensearch를 사용한 RAG챗봇 구성하기 1. KoSimCSE-RoBERTa를 사용한 한국어 문장 임베딩
SageMaker, Streamlit, Opensearch를 사용한 RAG챗봇 구성하기 2. KoSimCSE-RoBERTAa SageMaker Studio 테스트
SageMaker, Streamlit, Opensearch를 사용한 RAG챗봇 구성하기 4. FAQ with FAISS - Vector Store Test
SageMaker, Streamlit, Opensearch를 사용한 RAG챗봇 구성하기 5. FAQ with OpenSearch - Vector Store Test
KUULM LLM
KUULM(구름이라 칭합니다)은 고려대학교 NLP&AI Lab과 HIAI Lab이 개발한 한국어 LLM모델입니다.
https://github.com/nlpai-lab/KULLM
nlpai-lab/kullm-polyglot-5.8b-v2 · Hugging Face
EleutherAI/polyglot-ko-5.8b 모델을 parameter-efficient fine-tuned 한 버전입니다.
사용된 파라미터는 다음과 같습니다
- learning_rate: 3e-4
- train_batch_size: 128
- seed: 42
- distributed_type: multi-GPU (A100 80G)
- num_devices: 4
- gradient_accumulation_steps: 8
- optimizer: Adam with betas=(0.9,0.999) and epsilon=1e-08
- lr_scheduler_type: linear
- num_epochs: 8.0
사용된 프레임워크 종속성은 다음과 같습니다.
- Transformers 4.28.1
- Pytorch 2.0.0+cu117
- Datasets 2.11.0
- Tokenizers 0.13.3
LLM 설정
KULLM-Polyglot-5.8B-v2 모델은 G5.2xlarge GPU 1개를 사용하는 모델입니다.
util과 common_code를 먼저 루트에 위치시킵니다
! mkdir ./templates
! unzip utils.zip
! unzip common_code.zip
라이브러리와 노트북에 사용되는 변수를 선언하고 huggingface 모델과 경로를 설정합니다.
%load_ext autoreload
%autoreload 2
import sys
sys.path.append('./utils')
sys.path.append('./templates')
sys.path.append('./common_code')
import os
import sagemaker, boto3, jinja2
role = sagemaker.get_execution_role() # execution role for the endpoint
sess = sagemaker.session.Session() # sagemaker session for interacting with different AWS APIs
bucket = sess.default_bucket() # bucket to house artifacts
model_bucket = sess.default_bucket() # bucket to house artifacts
region = sess._region_name # region name of the current SageMaker Studio environment
account_id = sess.account_id() # account_id of the current SageMaker Studio environment
s3_client = boto3.client("s3") # client to intreract with S3 API
sm_client = boto3.client("sagemaker") # client to intreract with SageMaker
smr_client = boto3.client("sagemaker-runtime") # client to intreract with SageMaker Endpoints
jinja_env = jinja2.Environment() # jinja environment to generate model configuration templates
from huggingface_hub import snapshot_download
from pathlib import Path
model_id = "nlpai-lab/kullm-polyglot-5.8b-v2"
model_prefix = model_id.split('/')[-1].replace('.', '-')
s3_code_prefix = f"ko-llm/{model_prefix}/code" # folder within bucket where code artifact will go
s3_model_prefix = f"ko-llm/{model_prefix}/model" # folder where model checkpoint will go
해당 설정파일은 어떤 추론 라이브러리를 사용할지, 어떤 설정을 사용할지를 DJL Serving에 알려주는 설정파일입니다. 필요에 따라 적절한 구성을 설정합니다.
모델이 레이어에 따라 분할되는 파이프라인 병렬화를 사용하는 허깅페이스 Accelerate와 달리 DeepSpeed는 각 레이어(텐서)가 여러 디바이스에 걸처 샤딩되는 텐서 병렬화를 사용합니다. 그런다음 All-Gather연산을 통해 최종 결과를 계산하게 됩니다. 일반적으로 더 높은 GPU 활용률과 더 나은 성능을 제공합니다.
다음 코드를 사용하여 설정파일을 생성합니다.
src_path = f"src/{model_prefix}"
!rm -rf {src_path}
os.makedirs(src_path, exist_ok=True)
%%writefile {src_path}/serving.properties
engine=DeepSpeed
# passing extra options to model.py or built-in handler
job_queue_size=100
batch_size=1
max_batch_delay=1
max_idle_time=60
# Built-in entrypoint
#option.entryPoint=djl_python.deepspeed
# Hugging Face model id
option.model_id={{model_id}}
# defines custom environment variables
#env=SERVING_NUMBER_OF_NETTY_THREADS=2
# Allows to load DeepSpeed workers in parallel
option.parallel_loading=true
# specify tensor parallel degree (number of partitions)
option.tensor_parallel_degree=1
# specify per model timeout
option.model_loading_timeout=600
#option.predict_timeout=240
# mark the model as failure after python process crashing 10 times
retry_threshold=0
option.task=text-generation
Writing src/kullm-polyglot-5-8b-v2/serving.properties
다음 명령을 사용하여 S3 경로를 수정합니다.
# we plug in the appropriate model location into our `serving.properties` file based on the region in which this notebook is running
template = jinja_env.from_string(Path(f"{src_path}/serving.properties").open().read())
Path(f"{src_path}/serving.properties").open("w").write(template.render(model_id=model_id))
!pygmentize {src_path}/serving.properties | cat -n
1 engine=DeepSpeed
2
3 # passing extra options to model.py or built-in handler
4 job_queue_size=100
5 batch_size=1
6 max_batch_delay=1
7 max_idle_time=60
8
9 # Built-in entrypoint
10 #option.entryPoint=djl_python.deepspeed
11
12 # Hugging Face model id
13 option.model_id=nlpai-lab/kullm-polyglot-5.8b-v2
14
15 # defines custom environment variables
16 #env=SERVING_NUMBER_OF_NETTY_THREADS=2
17
18 # Allows to load DeepSpeed workers in parallel
19 option.parallel_loading=true
20
21 # specify tensor parallel degree (number of partitions)
22 option.tensor_parallel_degree=1
23
24 # specify per model timeout
25 option.model_loading_timeout=600
26 #option.predict_timeout=240
27
28 # mark the model as failure after python process crashing 10 times
29 retry_threshold=0
30
31 option.task=text-generation
모델 작성
커스텀 인퍼런스 코드를 작성하겠습니다.
%%writefile {src_path}/model.py
from djl_python import Input, Output
import os
import deepspeed
import torch
import logging
from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
from transformers import GPTNeoXLayer
predictor = None
def get_model(properties):
tp_degree = properties["tensor_parallel_degree"]
model_location = properties["model_dir"]
if "model_id" in properties:
model_location = properties["model_id"]
task = properties["task"]
logging.info(f"Loading model in {model_location}")
local_rank = int(os.getenv("LOCAL_RANK", "0"))
tokenizer = AutoTokenizer.from_pretrained(model_location)
model = AutoModelForCausalLM.from_pretrained(
model_location,
torch_dtype=torch.float16,
low_cpu_mem_usage=True,
)
model.requires_grad_(False)
model.eval()
ds_config = {
"tensor_parallel": {"tp_size": tp_degree},
"dtype": model.dtype,
"injection_policy": {
GPTNeoXLayer:('attention.dense', 'mlp.dense_4h_to_h')
}
}
logging.info(f"Starting DeepSpeed init with TP={tp_degree}")
model = deepspeed.init_inference(model, ds_config)
generator = pipeline(
task=task, model=model, tokenizer=tokenizer, device=local_rank
)
# <https://huggingface.co/docs/hub/models-tasks>
return generator
def handle(inputs: Input) -> None:
"""
inputs: Contains the configurations from serving.properties
"""
global predictor
if not predictor:
predictor = get_model(inputs.get_properties())
if inputs.is_empty():
# Model server makes an empty call to warmup the model on startup
logging.info("is_empty")
return None
data = inputs.get_as_json() #inputs.get_as_string()
logging.info("data:", data)
input_prompt, params = data["inputs"], data["parameters"]
result = predictor(input_prompt, **params)
logging.info("result:", result)
return Output().add_as_json(result) #Output().add(result)
Writing src/kullm-polyglot-5-8b-v2/model.py
타볼을 만들고 S3에 업로드합니다.
!rm -rf model.tar.gz
!tar czvf model.tar.gz -C {src_path} .
./ ./model.py ./serving.properties
s3_code_artifact = sess.upload_data("model.tar.gz", bucket, s3_code_prefix)
print(f"S3 Code or Model tar ball uploaded to --- > {s3_code_artifact}")
!rm -rf model.tar.gz
SageMaker Model을 만들겠습니다.
from sagemaker.utils import name_from_base
from sagemaker import image_uris
img_uri = image_uris.retrieve(framework="djl-deepspeed", region=region, version="0.23.0")
model_name = name_from_base(f"{model_prefix}")
print(model_name)
model_response = sm_client.create_model(
ModelName=model_name,
ExecutionRoleArn=role,
PrimaryContainer={"Image": img_uri, "ModelDataUrl": s3_code_artifact},
)
model_arn = model_response["ModelArn"]
print(f"Created Model: {model_arn}")
kullm-polyglot-5-8b-v2-2023-10-30-07-28-55-650 Created Model: arn:aws:sagemaker:ap-northeast-2:7xxxxxxxxxxxxx:model/kullm-polyglot-5-8b-v2-2023-10-30-07-28-55-650
모델 배포
SageMaker Endpoint를 생성하겠습니다.
해당 작업은 ml.g5.2xlarge 인스턴스 만큼의 비용이 발생하므로 이를 유의합니다. 1.212 USD / h
endpoint_config_name= f"{model_name}-config"
endpoint_name= f"{model_name}-endpoint"
variant_name= "variant1"
instance_type= "ml.g5.2xlarge"
initial_instance_count= 1
prod_variants = [
{
"VariantName": variant_name,
"ModelName": model_name,
"InstanceType": instance_type,
"InitialInstanceCount": initial_instance_count,
# "ModelDataDownloadTimeoutInSeconds": 2400,
"ContainerStartupHealthCheckTimeoutInSeconds": 1500, # at least 12min
}
]
endpoint_config_response= sm_client.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=prod_variants
)
endpoint_response= sm_client.create_endpoint(
EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name
)
print(f"Created Endpoint: {endpoint_response['EndpointArn']}")
Created Endpoint: arn:aws:sagemaker:ap-northeast-2:759320821027:endpoint/kullm-polyglot-5-8b-v2-2023-10-30-07-28-55-650-endpoint
from IPython.display import display, HTML
import time
def make_console_link(region, endpoint_name, task='[SageMaker LLM Serving]'):
endpoint_link = f' {task} Check Endpoint Status'
return endpoint_link
sess = sagemaker.session.Session() # sagemaker session for interacting with different AWS APIs
region = sess._region_name # region name of the current SageMaker Studio environment
endpoint_link = make_console_link(region, endpoint_name)
display(HTML(endpoint_link))
sagemaker.config INFO - Not applying SDK defaults from location: /etc/xdg/sagemaker/config.yaml sagemaker.config INFO - Not applying SDK defaults from location: /root/.config/sagemaker/config.yaml
[SageMaker LLM Serving] Check Endpoint Status
%%time
from inference_lib import describe_endpoint
describe_endpoint(endpoint_name)
Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is Creating Endpoint is InService CPU times: user 136 ms, sys: 18 ms, total: 154 ms Wall time: 8min 1s
Inference 테스트
엔드포인트를 호출할 때 이 텍스트를 JSON 페이로드 내에 제공해야 합니다. 이 JSON 페이로드에는 length, sampling strategy, output token sequence restrictions을 제어하는 데 도움이 되는 원하는 추론 매개변수가 포함될 수 있습니다. 허깅페이스 트랜스포머 transformers 라이브러리에는 사용 가능한 페이로드 매개변수의 전체 목록이 정의되어 있지만, 중요한 페이로드 매개변수는 다음과 같이 정의되어 있습니다:
- do_sample (bool) – logits sampling 활성화
- max_new_tokens (int) – 생성된 토큰의 최대 수
- best_of (int) – best_of 개의 시퀀스를 생성하고 가장 높은 토큰 로그 확률이 있는 경우 반환
- repetition_penalty (float) – 반복 패널티에 대한 파라미터, 1.0은 패널티가 없음을 의미하여 Greedy 서치와 동일, 커질수록 다양한 결과를 얻을 수 있으며, 자세한 사항은 this paper을 참고
- return_full_text (bool) – 생성된 텍스트에 프롬프트를 추가할지 여부
- seed (int) – Random sampling seed
- stop_sequences (List[str]) – stop_sequences 가 생성되면 토큰 생성을 중지
- temperature (float) – logits 분포 모듈화에 사용되는 값
- top_k (int) – 상위 K개 만큼 가장 높은 확률 어휘 토큰의 수
- top_p (float) – 1 보다 작게 설정하게 되며, 상위부터 정렬했을 때 가능한 토큰들의 확률을 합산하여 top_p 이상의 가장 작은 집합을 유지
- truncate (int) – 입력 토큰을 지정된 크기로 잘라냄
- typical_p (float) – typical Decoding 양으로, 자세한 사항은 Typical Decoding for Natural Language Generation을 참고
- watermark (bool) – A Watermark for Large Language Models가 Watermarking
- decoder_input_details (bool) – decoder input token logprobs와 ids를 반환
params = {
"do_sample": False,
"max_new_tokens": 256,
"temperature": 0.4,
"top_p": 0.9,
"return_full_text": False,
"repetition_penalty": 1.1,
"presence_penalty": None,
"eos_token_id": 2,
}
import json
from inference_utils import KoLLMSageMakerEndpoint
pred = KoLLMSageMakerEndpoint(endpoint_name)
instruction = "다음 글을 요약해 주세요."
context = """
엔터프라이즈 환경에서 생성 AI와 대규모 언어 모델(LLM; Large Language Models)의 가장 일반적인 유스케이스 중 하나는 기업의 지식 코퍼스를 기반으로 질문에 답변하는 것입니다. Amazon Lex는 AI 기반 챗봇을 구축하기 위한 프레임워크를 제공합니다. 사전 훈련된 파운데이션 모델(FM; Foundation Models)은 다양한 주제에 대한 요약, 텍스트 생성, 질문 답변과 같은 자연어 이해(NLU; Natural Language Understanding) 작업은 잘 수행하지만, 훈련 데이터의 일부로 보지 못한 콘텐츠에 대한 질문에는 정확한(오답 없이) 답변을 제공하는 데 어려움을 겪거나 완전히 실패합니다. 또한 FM은 특정 시점의 데이터 스냅샷으로 훈련하기에 추론 시점에 새로운 데이터에 액세스할 수 있는 고유한 기능이 없기에 잠재적으로 부정확하거나 부적절한 답변을 제공할 수 있습니다.
이 문제를 해결하기 위해 흔히 사용되는 접근 방식은 검색 증강 생성(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 블로그를 참조하기 바랍니다.
"""
payload = pred.get_payload(instruction, context, params)
payload
{'inputs': '아래는 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력이 짝을 이루는 예제입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n다음 글을 요약해 주세요.\n\n### 입력:\n\n엔터프라이즈 환경에서 생성 AI와 대규모 언어 모델(LLM; Large Language Models)의 가장 일반적인 유스케이스 중 하나는 기업의 지식 코퍼스를 기반으로 질문에 답변하는 것입니다. Amazon Lex는 AI 기반 챗봇을 구축하기 위한 프레임워크를 제공합니다. 사전 훈련된 파운데이션 모델(FM; Foundation Models)은 다양한 주제에 대한 요약, 텍스트 생성, 질문 답변과 같은 자연어 이해(NLU; Natural Language Understanding) 작업은 잘 수행하지만, 훈련 데이터의 일부로 보지 못한 콘텐츠에 대한 질문에는 정확한(오답 없이) 답변을 제공하는 데 어려움을 겪거나 완전히 실패합니다. 또한 FM은 특정 시점의 데이터 스냅샷으로 훈련하기에 추론 시점에 새로운 데이터에 액세스할 수 있는 고유한 기능이 없기에 잠재적으로 부정확하거나 부적절한 답변을 제공할 수 있습니다.\n\n이 문제를 해결하기 위해 흔히 사용되는 접근 방식은 검색 증강 생성(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\n\n### 응답:\n', 'parameters': {'do_sample': False, 'max_new_tokens': 256, 'temperature': 0.4, 'top_p': 0.9, 'return_full_text': False, 'repetition_penalty': 1.1, 'presence_penalty': None, 'eos_token_id': 2}}
%%time
generated_text = pred.infer(payload, verbose=True)
('Response: 이 글에서는 기업의 지식 코퍼스를 기반으로 질문에 답변하는 것과 관련된 문제를 해결하기 위한 방법으로 검색 증강 ' '생성(RAG)이라는 기법을 소개하고 있습니다. 이 기법은 사전 훈련된 모델을 사용하여 사용자 질문을 벡터 임베딩으로 변환한 다음, 사전 ' '훈련된 모델을 사용하여 사용자 질문에 대한 답변을 생성합니다. 이 접근 방식은 매개변수 메모리는 사전 훈련된 모델이고 비매개변수 메모리는 ' '사전 훈련된 신경망 검색기로 액세스되는 위키백과의 고밀도 벡터 색인 모델을 사용합니다.') CPU times: user 14.5 ms, sys: 423 µs, total: 14.9 ms Wall time: 7.89 s
endpoint_name_text = endpoint_name
%store endpoint_name_text
Stored 'endpoint_name_text' (str)