생성 데이터 인텔리전스

Hugging Face 및 LoRA를 사용하여 단일 Amazon SageMaker GPU에서 대규모 언어 모델 교육 | 아마존 웹 서비스

시간

이 게시물은 Hugging Face의 Philipp Schmid와 공동으로 작성되었습니다.

우리 모두는 대규모 언어 모델(LLM) 분야의 발전과 LLM이 귀중한 통찰력을 제공하는 문제 세트의 수가 계속 증가하고 있다는 소식을 들었습니다. 대규모 데이터 세트와 여러 작업에 대해 훈련된 대형 모델은 특별히 훈련되지 않은 작업에 대해서도 잘 일반화할 수 있습니다. 이러한 모델을 호출합니다. 기초 모델, 처음 대중화 된 용어 인간 중심 인공 지능을 위한 스탠포드 연구소. 이러한 기초 모델은 특히 다음의 도움으로 잘 일반화할 수 있지만 신속한 엔지니어링 기술, 종종 사용 사례가 도메인에 따라 다르거나 작업이 너무 다르기 때문에 모델에 추가 사용자 정의가 필요합니다. 특정 도메인 또는 작업에 대한 대규모 모델의 성능을 향상시키는 한 가지 접근 방식은 더 작은 작업별 데이터 세트로 모델을 추가로 훈련하는 것입니다. 이 접근 방식은 미세 조정, 성공적으로 LLM의 정확도를 향상시키려면 모든 모델 가중치를 수정해야 합니다. 미세 조정은 데이터 세트 크기가 훨씬 작기 때문에 모델의 사전 교육보다 훨씬 빠르지만 여전히 상당한 컴퓨팅 성능과 메모리가 필요합니다. 미세 조정은 원본 모델의 모든 매개 변수 가중치를 수정하므로 비용이 많이 들고 원본과 동일한 크기의 모델이 생성됩니다.

이러한 문제를 해결하기 위해 포옹하는 얼굴 소개 파라미터 효율적인 미세 조정 라이브러리 (PEFT). 이 라이브러리를 사용하면 대부분의 원래 모델 가중치를 동결하고 훨씬 더 작은 추가 매개변수 세트를 교육하여 모델 레이어를 대체하거나 확장할 수 있습니다. 따라서 필요한 컴퓨팅 및 메모리 측면에서 훈련 비용이 훨씬 저렴합니다.

이 게시물에서는 7억 매개변수를 훈련하는 방법을 보여줍니다. BloomZ 모델 하나의 그래픽 처리 장치(GPU)만 사용하여 아마존 세이지 메이커, 고품질 ML 모델을 준비, 구축, 교육 및 배포하기 위한 Amazon의 ML(기계 학습) 플랫폼입니다. BloomZ는 범용 자연어 처리(NLP) 모델입니다. PEFT를 사용하여 메신저와 같은 대화를 요약하는 특정 작업에 대해 이 모델을 최적화합니다. 우리가 사용하는 단일 GPU 인스턴스는 AWS가 제공하는 많은 인스턴스 유형 중 저렴한 예입니다. 단일 GPU에서 이 모델을 교육하는 것은 가장 비용 효율적인 AI/ML 서비스 공급자가 되려는 AWS의 노력을 강조합니다.

이 연습에 대한 코드는 Hugging Face 노트북 GitHub 리포지토리 아래에서 찾을 수 있습니다. 세이지메이커/24_train_bloom_peft_lora 폴더에 있습니다.

사전 조건

따라 하려면 다음과 같은 전제 조건이 있어야 합니다.

  • An AWS 계정.
  • 내부의 Jupyter 노트북 아마존 세이지 메이커 스튜디오 또는 SageMaker 노트북 인스턴스.
  • 단일 NVIDIA A5.2G GPU가 포함된 SageMaker ml.g10xlarge 인스턴스 유형에 액세스해야 합니다. 에 AWS 관리 콘솔, SageMaker에 대한 서비스 할당량으로 이동하여 다음 할당량에 대해 인스턴스 1개 증가를 요청합니다. 학습 작업 사용을 위한 ml.g5.2xlarge학습 작업 사용을 위한 ml.g5.2xlarge.
  • 요청한 할당량이 계정에 적용된 후 ml.t3.medium 인스턴스와 함께 기본 Studio Python 3(Data Science) 이미지를 사용하여 노트북 코드 조각을 실행할 수 있습니다. 사용 가능한 커널의 전체 목록은 다음을 참조하십시오. 사용 가능한 Amazon SageMaker 커널.

SageMaker 세션 설정

다음 코드를 사용하여 SageMaker 세션을 설정합니다.

import sagemaker
import boto3
sess = sagemaker.Session()
# sagemaker session bucket -> used for uploading data, models and logs
# sagemaker will automatically create this bucket if it does not exist
sagemaker_session_bucket=None
if sagemaker_session_bucket is None and sess is not None: # set to default bucket if a bucket name is not given sagemaker_session_bucket = sess.default_bucket() try: role = sagemaker.get_execution_role()
except ValueError: iam = boto3.client('iam') role = iam.get_role(RoleName='sagemaker_execution_role')['Role']['Arn'] sess = sagemaker.Session(default_bucket=sagemaker_session_bucket) print(f"sagemaker role arn: {role}")
print(f"sagemaker bucket: {sess.default_bucket()}")
print(f"sagemaker session region: {sess.boto_region_name}")

데이터 세트 로드 및 준비

우리는을 사용하여 삼섬 요약이 포함된 16,000개의 메신저 같은 대화 모음인 데이터 세트. 대화는 영어에 능통한 언어학자에 의해 만들어지고 기록되었습니다. 다음은 데이터 세트의 예입니다.

{ "id": "13818513", "summary": "Amanda baked cookies and will bring Jerry some tomorrow.", "dialogue": "Amanda: I baked cookies. Do you want some?rnJerry: Sure!rnAmanda: I'll bring you tomorrow :-)"
}

모델을 교육하려면 입력(텍스트)을 토큰 ID로 변환해야 합니다. 이것은 Hugging Face Transformers 토크나이저에 의해 수행됩니다. 자세한 내용은 다음을 참조하십시오. 장 6 Hugging Face NLP 과정의.

다음 코드를 사용하여 입력을 변환합니다.

from transformers import AutoTokenizer model_id="bigscience/bloomz-7b1" # Load tokenizer of BLOOMZ
tokenized = AutoTokenizer.from_pretrained(model_id)
tokenizer.model_max_length = 2048 # overwrite wrong value

학습을 시작하기 전에 데이터를 처리해야 합니다. 학습이 완료되면 모델은 일련의 텍스트 메시지를 입력으로 사용하고 요약을 출력으로 생성합니다. 올바른 응답(요약)이 포함된 프롬프트(메시지)로 데이터 형식을 지정해야 합니다. 또한 모델 교육을 최적화하려면 예제를 더 긴 입력 시퀀스로 청크해야 합니다. 다음 코드를 참조하십시오.

from random import randint
from itertools import chain
from functools import partial # custom instruct prompt start
prompt_template = f"Summarize the chat dialogue:n{{dialogue}}n---nSummary:n{{summary}}{{eos_token}}" # template dataset to add prompt to each sample
def template_dataset(sample): sample["text"] = prompt_template.format(dialogue=sample["dialogue"], summary=sample["summary"], eos_token=tokenizer.eos_token) return sample # apply prompt template per sample
dataset = dataset.map(template_dataset, remove_columns=list(dataset.features)) print(dataset[randint(0, len(dataset))]["text"]) # empty list to save remainder from batches to use in next batch
remainder = {"input_ids": [], "attention_mask": []} def chunk(sample, chunk_length=2048): # define global remainder variable to save remainder from batches to use in next batch global remainder # Concatenate all texts and add remainder from previous batch concatenated_examples = {k: list(chain(*sample[k])) for k in sample.keys()} concatenated_examples = {k: remainder[k] + concatenated_examples[k] for k in concatenated_examples.keys()} # get total number of tokens for batch batch_total_length = len(concatenated_examples[list(sample.keys())[0]]) # get max number of chunks for batch if batch_total_length >= chunk_length: batch_chunk_length = (batch_total_length // chunk_length) * chunk_length # Split by chunks of max_len. result = { k: [t[i : i + chunk_length] for i in range(0, batch_chunk_length, chunk_length)] for k, t in concatenated_examples.items() } # add remainder to global variable for next batch remainder = {k: concatenated_examples[k][batch_chunk_length:] for k in concatenated_examples.keys()} # prepare labels result["labels"] = result["input_ids"].copy() return result # tokenize and chunk dataset
lm_dataset = dataset.map( lambda sample: tokenizer(sample["text"]), batched=True, remove_columns=list(dataset.features)
).map( partial(chunk, chunk_length=2048), batched=True,
) # Print total number of samples
print(f"Total number of samples: {len(lm_dataset)}")

이제 사용할 수 있습니다 파일 시스템 통합 데이터 세트를 업로드하려면 아마존 단순 스토리지 서비스 (Amazon S3) :

# save train_dataset to s3
training_input_path = f's3://{sess.default_bucket()}/processed/samsum-sagemaker/train'
lm_dataset.save_to_disk(training_input_path) print("uploaded data to:")
print(f"training dataset to: {training_input_path}") In [ ]:
training_input_path="s3://sagemaker-us-east-1-558105141721/processed/samsum-sagemaker/train"

SageMaker에서 LoRA 및 bitsandbytes int-7로 BLOOMZ-8B 미세 조정

포옹하는 얼굴 BLOOMZ-7B 모델 카드 초기 교육이 각각 8개의 A8 100GB GPU와 80GB 메모리 CPU가 있는 512개 노드에 분산되었음을 나타냅니다. 이 컴퓨팅 구성은 쉽게 액세스할 수 없고 소비자에게 비용이 많이 들며 분산 교육 성능 최적화에 대한 전문 지식이 필요합니다. SageMaker는 다음을 통해 이 설정의 복제에 대한 장벽을 낮춥니다. 분산 교육 라이브러리; 그러나 유사한 4개의 온디맨드 ml.p24de.376.88xlarge 인스턴스 비용은 시간당 $40입니다. 또한 완전히 훈련된 모델은 약 XNUMXGB의 메모리를 사용하는데, 이는 많은 개별 소비자가 사용할 수 있는 GPU의 사용 가능한 메모리를 초과하고 대규모 모델 추론을 처리하기 위한 전략이 필요합니다. 결과적으로 여러 모델 실행 및 배포에 대한 작업에 대한 모델의 전체 미세 조정에는 소비자가 쉽게 액세스할 수 없는 하드웨어에 상당한 컴퓨팅, 메모리 및 스토리지 비용이 필요합니다.

우리의 목표는 정확성을 유지하면서 BLOOMZ-7B를 채팅 요약 사용 사례에 보다 접근하기 쉽고 비용 효율적인 방법으로 적용하는 방법을 찾는 것입니다. 단일 소비자 등급 NVIDIA A5.2G GPU가 있는 SageMaker ml.g10xlarge 인스턴스에서 모델을 미세 조정할 수 있도록 미세 조정을 위한 컴퓨팅 및 메모리 요구 사항을 줄이는 두 가지 기술인 LoRA 및 양자화를 사용합니다.

LoRA(Low Rank Adaptation)는 예측 성능의 손실 없이 새로운 작업을 미세 조정하는 데 필요한 모델 매개변수 및 관련 컴퓨팅의 수를 크게 줄이는 기술입니다. 먼저 원래 모델 가중치를 동결하고 전체 가중치를 업데이트하는 대신 새 작업에 맞게 더 작은 순위 분해 가중치 행렬을 최적화한 다음 이렇게 조정된 가중치를 원래 모델에 다시 주입합니다. 결과적으로 가중치 기울기 업데이트가 적다는 것은 미세 조정 중에 컴퓨팅 및 GPU 메모리가 적다는 것을 의미합니다. 이 접근법의 배후에 있는 직관은 LoRA를 통해 LLM이 중복되고 덜 중요한 토큰을 무시하면서 가장 중요한 입력 및 출력 토큰에 집중할 수 있다는 것입니다. LoRA 기술에 대한 이해를 심화하려면 원본 문서를 참조하십시오. LoRA: 대규모 언어 모델의 낮은 순위 적응.

LoRA 기술 외에도 다음을 사용합니다. bitsanbytes 포옹 얼굴 통합 LLM.int8() 고정된 BloomZ 모델을 양자화하거나 float16에서 int8로 반올림하여 가중치 및 편향 값의 정밀도를 줄이는 메서드입니다. 양자화는 BloomZ에 필요한 메모리를 약 10배로 줄여 예측 성능의 큰 손실 없이 모델을 A8G GPU 인스턴스에 맞출 수 있습니다. intXNUMX 양자화가 작동하는 방식, bitsandbytes 라이브러리에서의 구현 및 Hugging Face Transformers 라이브러리와의 통합에 대한 이해를 심화하려면 다음을 참조하십시오. Hugging Face Transformers, Accelerate 및 비트샌드바이트를 사용하는 대규모 변압기용 8비트 매트릭스 곱셈에 대한 부드러운 소개.

Hugging Face는 PEFT 라이브러리 및 bitsandbytes 라이브러리와의 통합을 통해 광범위한 변환기 모델에서 LoRA 및 양자화에 액세스할 수 있도록 했습니다. 준비된 스크립트의 create_peft_config() 함수 run_clm.py 교육을 위해 모델을 준비하는 데 사용되는 방법을 보여줍니다.

def create_peft_config(model): from peft import ( get_peft_model, LoraConfig, TaskType, prepare_model_for_int8_training, ) peft_config = LoraConfig( task_type=TaskType.CAUSAL_LM, inference_mode=False, r=8, # Lora attention dimension. lora_alpha=32, # the alpha parameter for Lora scaling. lora_dropout=0.05, # the dropout probability for Lora layers. target_modules=["query_key_value"], ) # prepare int-8 model for training model = prepare_model_for_int8_training(model) model = get_peft_model(model, peft_config) model.print_trainable_parameters() return model

LoRA를 사용하여 print_trainable_parameters()의 출력은 모델 매개변수의 수를 7억에서 3.9만으로 줄일 수 있었음을 나타냅니다. 이는 원래 모델 매개변수의 5.6%만 업데이트하면 된다는 의미입니다. 이렇게 컴퓨팅 및 메모리 요구 사항이 크게 감소하여 문제 없이 모델을 GPU에 맞추고 훈련할 수 있습니다.

를 만들려면 SageMaker 교육 작업을 수행하려면 Hugging Face 추정기가 필요합니다. 추정기는 종단 간 SageMaker 교육 및 배포 작업을 처리합니다. SageMaker는 필요한 모든 시작 및 관리를 처리합니다. 아마존 엘라스틱 컴퓨트 클라우드 (Amazon EC2) 인스턴스. 또한 올바른 Hugging Face 교육 컨테이너를 제공하고 제공된 스크립트를 업로드하며 S3 버킷에서 경로의 컨테이너로 데이터를 다운로드합니다. /opt/ml/input/data. 그런 다음 훈련 작업을 시작합니다. 다음 코드를 참조하십시오.

import time
# define Training Job Name job_name = f'huggingface-peft-{time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())}' from sagemaker.huggingface import HuggingFace # hyperparameters, which are passed into the training job
hyperparameters ={ 'model_id': model_id, # pre-trained model 'dataset_path': '/opt/ml/input/data/training', # path where sagemaker will save training dataset 'epochs': 3, # number of training epochs 'per_device_train_batch_size': 1, # batch size for training 'lr': 2e-4, # learning rate used during training
} # create the Estimator
huggingface_estimator = HuggingFace( entry_point = 'run_clm.py', # train script source_dir = 'scripts', # directory which includes all the files needed for training instance_type = 'ml.g5.2xlarge', # instances type used for the training job instance_count = 1, # the number of instances used for training base_job_name = job_name, # the name of the training job role = role, # IAM role used in training job to access AWS resources, e.g. S3 volume_size = 300, # the size of the EBS volume in GB transformers_version = '4.26', # the transformers version used in the training job pytorch_version = '1.13', # the pytorch_version version used in the training job py_version = 'py39', # the python version used in the training job hyperparameters = hyperparameters
)

이제 .fit() 메서드를 사용하고 훈련 스크립트에 S3 경로를 전달하여 훈련 작업을 시작할 수 있습니다.

# define a data input dictionary with our uploaded s3 uris
data = {'training': training_input_path} # starting the train job with our uploaded datasets as inputs
huggingface_estimator.fit(data, wait=True)

LoRA 및 양자화를 사용하면 BLOOMZ-7B를 SageMaker로 저렴하고 효율적으로 작업에 미세 조정할 수 있습니다. SageMaker 교육 작업을 사용하는 경우 모델 교육 기간 동안 GPU 비용만 지불하면 됩니다. 이 예에서 SageMaker 훈련 작업은 20,632초가 걸렸으며, 이는 약 5.7시간입니다. 우리가 사용한 ml.g5.2xlarge 인스턴스는 주문형 사용에 대해 시간당 $1.515의 비용이 듭니다. 결과적으로 미세 조정된 BLOOMZ-7B 모델을 교육하는 데 드는 총 비용은 $8.63에 불과했습니다. 상대적으로, Hugging Face 모델 카드에 설명된 원래 컴퓨팅 구성에서 선형 GPU 확장을 가정할 때 모델의 7억 가중치에 대한 전체 미세 조정 비용은 훈련 실행당 약 $600 또는 6,900% 더 비쌉니다. 실제로 이것은 교육 전략, 인스턴스 선택 및 인스턴스 요금에 따라 더 달라집니다.

우리는 또한 다음을 사용하여 교육 비용을 더 줄일 수 있습니다. SageMaker 관리형 스팟 인스턴스. 그러나 이로 인해 스팟 인스턴스 중단으로 인해 총 훈련 시간이 증가할 가능성이 있습니다. 보다 Amazon SageMaker 요금 인스턴스 가격 세부 정보.

추론을 위해 SageMaker 엔드포인트에 모델 배포

LoRA를 사용하면 이전에 새 작업에 더 작은 가중치 세트를 적용했습니다. 이러한 작업별 가중치를 원래 모델의 사전 훈련된 가중치와 결합하는 방법이 필요합니다. run_clm.py 스크립트에서 PEFT 라이브러리 merge_and_unload() 이 방법은 기본 BLOOMZ-7B 모델을 작업에 맞게 미세 조정된 업데이트된 어댑터 가중치와 병합하여 원래 모델과 비교하여 추론 대기 시간을 도입하지 않고 더 쉽게 배포할 수 있도록 합니다.

이 섹션에서는 미세 조정된 모델 아티팩트에서 SageMaker 모델을 생성하고 추론을 위해 SageMaker 엔드포인트에 배포하는 단계를 살펴봅니다. 먼저 SageMaker 엔드포인트에 배포하기 위해 미세 조정된 새 모델 아티팩트를 사용하여 Hugging Face 모델을 생성할 수 있습니다. 이전에 SageMaker Hugging Face estimator로 모델을 교육했기 때문에 모델을 즉시 배포할 수 있습니다. 대신 훈련된 모델을 S3 버킷에 업로드하고 이를 사용하여 나중에 모델 패키지를 생성할 수 있습니다. 다음 코드를 참조하십시오.

from sagemaker.huggingface import HuggingFaceModel # 1. create Hugging Face Model Class
huggingface_model = HuggingFaceModel( model_data=huggingface_estimator.model_data, #model_data="s3://hf-sagemaker-inference/model.tar.gz", # Change to your model path role=role, transformers_version="4.26", pytorch_version="1.13", py_version="py39", model_server_workers=1
)

SageMaker 추정기와 마찬가지로 Hugging Face 추정기 객체의 deploy() 메서드를 사용하여 원하는 수와 유형의 인스턴스를 전달하여 모델을 배포할 수 있습니다. 이 예에서는 이전 단계에서 모델이 미세 조정된 단일 NVIDIA A5g GPU가 장착된 동일한 G10 인스턴스 유형을 사용합니다.

# 2. deploy model to SageMaker Inference
predictor = huggingface_model.deploy( initial_instance_count=1, instance_type= "ml.g5.4xlarge"
)

추론 요청을 수락할 준비를 위해 SageMaker 엔드포인트가 인스턴스를 온라인으로 전환하고 모델을 다운로드하는 데 5~10분이 걸릴 수 있습니다.

엔드포인트가 실행 중이면 데이터 세트 테스트 분할에서 샘플 대화를 전송하여 테스트할 수 있습니다. 먼저 Hugging Face Datasets 라이브러리를 사용하여 테스트 분할을 로드합니다. 다음으로, 데이터 세트 배열에서 단일 테스트 샘플을 인덱스 슬라이싱하기 위한 임의의 정수를 선택합니다. 문자열 형식을 사용하여 테스트 샘플을 프롬프트 템플릿과 구조화된 입력으로 결합하여 모델의 응답을 안내합니다. 그런 다음 이 구조화된 입력을 추가 모델 입력 매개변수와 결합하여 형식이 지정된 샘플 JSON 페이로드로 만들 수 있습니다. 마지막으로 형식이 지정된 샘플로 SageMaker 엔드포인트를 호출하고 샘플 대화 상자를 요약하는 모델의 출력을 인쇄합니다. 다음 코드를 참조하십시오.

from random import randint
from datasets import load_dataset # 1. Load dataset from the hub
test_dataset = load_dataset("samsum", split="test") # 2. select a random test sample
sample = test_dataset[randint(0,len(test_dataset))] # 3. format the sample
prompt_template = f"Summarize the chat dialogue:n{{dialogue}}n---nSummary:n" fomatted_sample = { "inputs": prompt_template.format(dialogue=sample["dialogue"]), "parameters": { "do_sample": True, # sample output predicted probabilities "top_p": 0.9, # sampling technique Fan et. al (2018) "temperature": 0.1, # increasing the likelihood of high probability words and decreasing the likelihood of low probability words "max_new_tokens": 100, # }
} # 4. Invoke the SageMaker endpoint with the formatted sample
res = predictor.predict(fomatted_sample) # 5. Print the model output
print(res[0]["generated_text"].split("Summary:")[-1])
# Sample model output: Kirsten and Alex are going bowling this Friday at 7 pm. They will meet up and then go together.

이제 모델 요약 대화 출력을 테스트 샘플 요약과 비교해 보겠습니다.

print(sample["summary"])
# Sample model input: Kirsten reminds Alex that the youth group meets this Friday at 7 pm to go bowling.

정리

이제 모델을 테스트했으므로 연결된 SageMaker 리소스를 정리하여 요금이 계속 청구되지 않도록 해야 합니다.

predictor.delete_model()
predictor.delete_endpoint()

요약

이 게시물에서는 SageMaker와 함께 Hugging Face Transformer, PEFT 및 bitsandbytes 라이브러리를 사용하여 $8에 단일 GPU에서 BloomZ 대규모 언어 모델을 미세 조정한 다음 테스트 샘플에 대한 추론을 위해 모델을 SageMaker 엔드포인트에 배포했습니다. SageMaker는 Hugging Face 모델을 사용하는 다양한 방법을 제공합니다. 더 많은 예를 보려면 다음을 확인하세요. AWS 샘플 GitHub.

SageMaker를 계속 사용하여 기초 모델을 미세 조정하려면 게시물의 몇 가지 기술을 시도하십시오. Amazon SageMaker에서 맞춤형 생성 AI SaaS 애플리케이션 설계. 또한 탐색을 통해 Amazon Generative AI 기능에 대해 자세히 알아볼 것을 권장합니다. 점프 시작아마존 타이탄 모델 및 아마존 기반암.


저자에 관하여

필립 슈 미드 Hugging Face의 기술 책임자이며 오픈 소스와 오픈 과학을 통해 좋은 기계 학습을 민주화한다는 사명을 가지고 있습니다. Philipp은 최첨단 및 생성 AI 기계 학습 모델을 생산하는 데 열정적입니다. 그는 Data Science on AWS와 같은 다양한 모임에서 AI 및 NLP에 대한 지식을 공유하는 것을 좋아합니다. 블로그.

로버트 피셔 의료 및 생명 과학 고객을 위한 선임 솔루션 아키텍트입니다. 그는 고객과 긴밀히 협력하여 AWS가 특히 AI/ML 공간에서 문제를 해결하는 데 어떻게 도움이 되는지 이해합니다. Robert는 의료 기기, 핀테크, 소비자 대면 애플리케이션을 포함한 다양한 산업 분야에서 소프트웨어 엔지니어링 분야에서 다년간의 경험을 보유하고 있습니다.

더그 켈리 기계 학습 플랫폼, 자율 주행 차량에서 정밀 농업에 이르기까지 수직 분야에서 최고의 기계 학습 신생 기업에 대한 신뢰할 수 있는 기술 고문 역할을 하는 AWS 선임 솔루션 아키텍트입니다. 그는 MLOps 및 ML 추론 워크로드로 고객 지원을 전문으로 하는 AWS ML 기술 현장 커뮤니티의 회원입니다.

spot_img

최신 인텔리전스

spot_img

우리와 함께 채팅

안녕하세요! 어떻게 도와 드릴까요?