거대 언어 모델 튜닝을 위한 미니멀리스트 접근법: 2부 - QLoRA로 학습하기

2024. 5. 21.Tech

들어가며

 

안녕하세요. 케이뱅크 데이터서비스팀에서 AI 업무를 하고 있는 김하영입니다. 

 

이번 포스팅에서는 PEFT 방법 중 가장 핵심이 되는 QLoRA에 대해 실제코드를 살펴보고 학습 결과를 확인해 보겠습니다.

 

 

 

1부 되짚어 보기

 

지난 포스팅에서는 거대 언어 모델(LLM)을 조정하는 세 가지 방법에 대해 살펴보았습니다.

 

In-context Learning은 프롬프트 입력 시 연관된 예시를 함께 제공하여 모델의 가중치를 업데이트하지 않고 프롬프트 엔지니어링만으로 문제를 해결하는 방법입니다. 사용자가 제공하는 예시의 수에 따라 Zero-shot 또는 Few-shot Learning 등으로 구분됩니다.

 

전체 파인튜닝(Full Fine-tuning)은 사전 학습된 모델(Pre-trained Model, PLM)의 모든 파라미터를 재학습하여 가중치를 업데이트하는 방식으로, LLM의 범용적인 성능을 특정 작업이나 도메인에 최적화하는 방법입니다.

 

PEFT(Paramter Efficient Fine-tuning)는 모델의 전체 파라미터 중 일부만을 선택적으로 조정함으로써, 모델의 핵심 구조를 유지하면서도 특정 기능이나 작업에 맞게 모델을 조정하는 방법입니다. PEFT의 대표적인 방법으로는 LoRA(Low-rank Adaptation) 및 LoRA에 4비트 양자화를 적용하여 더욱 경량화한 QLoRA(Quantized LoRA가 있습니다.

 

 

 

QLoRA 학습 목표


우리는 이번 실습에서 한국어로 학습된 베이스 모델을 QLoRA를 통해 튜닝하여, 질문에 답변하는 모델을 만들어 보겠습니다. 

LLM에 질문을 입력으로 전달하면 당연히 질문에 대한 답변할 것이라고 생각할 수 있지만, 실제로는 그렇지 않을 수 있습니다. 어떤 모델은 질문에 답변하는 형태 대신, 입력된 문장에 이어서 문장을 계속 작성할 수도 있습니다. 이처럼 LLM의 기능은 매우 다양하며, 특정 목적에 맞게 튜닝하여 원하는 성능을 이끌어낼 수 있습니다. 이번 실습에서는 이러한 LLM의 기능을 이해하고, 질문-답변 형식의 모델을 만들어봄으로써 LLM의 활용 방법을 경험해 보겠습니다.

 

베이스 모델로는 야놀자에서 오픈소스로 공개한 'EEVE-Korean-10.8B-v1.0'을 사용합니다. 이 모델은 업스테이지의 SOLAR-10.7B-v1.0 모델을 한국어 어휘로 학습한 모델입니다. 모델에 대한 자세한 설명은 https://huggingface.co/yanolja/EEVE-Korean-10.8B-v1.0에서 확인하실 수 있습니다.

 

실습 환경은 Google Colab이며, Colab Pro 요금제를 통해 NVIDIA A100(40GB) GPU를 사용합니다. 만약 더 낮은 사양의 GPU를 선택할 경우, 학습이 불가능할 수 있으므로 참고하시기 바랍니다.

 

정리하면 아래와 같습니다.

  • 베이스 모델 : yanolja/EEVE-Korean-10.8B-v1.0
  • 학습 목표 : 사용자의 질문에 답변하는 형식의 모델 만들기
  • 실행 환경 : Google Colab - A100 40GB

 

 

QLoRA 학습 전 테스트

 

QLoRA를 통한 파인튜닝을 하기 전에, 베이스 모델이 어떤 식으로 텍스트를 생성하는지 확인해 보겠습니다.

 

구글 드라이브 마운트

먼저, 구글 드라이브를 사용하기 위해 마운트합니다. Colab을 사용하지 않는 경우, 이 과정은 건너뛰시면 됩니다.

import os
from google.colab import drive

drive.mount('/content/drive')
print(os.getcwd())
os.chdir('/content/drive/MyDrive/Colab Notebooks')
print(os.getcwd())

 

모듈 불러오기

미설치된 모듈이 있을 경우, pip install 명령어를 통해 필요한 모듈을 설치합니다.

!pip install -q -U datasets
!pip install -q -U bitsandbytes
!pip install -q -U accelerate
#!pip install -q -U git+https://github.com/huggingface/accelerate.git
!pip install -q -U peft
!pip install -q -U trl

 

모듈을 import합니다.

import os
import torch
import transformers
from datasets import load_from_disk
from transformers import (
    BitsAndBytesConfig,
    AutoModelForCausalLM,
    AutoTokenizer,
    Trainer,
    TextStreamer,
    pipeline
)
from peft import (
    LoraConfig,
    prepare_model_for_kbit_training,
    get_peft_model,
    get_peft_model_state_dict,
    set_peft_model_state_dict,
    TaskType,
    PeftModel
)
from trl import SFTTrainer

 

모델 및 토크나이저 불러오기

BASE_MODEL = "yanolja/EEVE-Korean-10.8B-v1.0"

model = AutoModelForCausalLM.from_pretrained(BASE_MODEL, load_in_4bit=True, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)

 

베이스 모델 추론 테스트

prompt = "한국의 아이돌 문화에 대해 알려줘."

# 텍스트 생성을 위한 파이프라인 설정
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=256) # max_new_tokens: 생성할 최대 토큰 수
outputs = pipe(
    prompt,
    do_sample=True, # 샘플링 전략 사용. 확률 분포를 기반으로 다음 토큰을 선택
    temperature=0.2, # 샘플링의 다양성을 조절하는 파라미터. 값이 높을수록 랜덤성 증가
    top_k=50, # 다음 토큰을 선택할 때 상위 k개의 후보 토큰 중에서 선택. 여기에서는 상위 50개의 후보 토큰 중에서 샘플링
    top_p=0.95, # 누적 확률이 p가 될 때까지 후보 토큰을 포함
    repetition_penalty=1.2, # 반복 패널티를 적용하여 같은 단어나 구절이 반복되는 것 방지
    add_special_tokens=True # 모델이 입력 프롬프트의 시작과 끝을 명확히 인식할 수 있도록 특별 토큰 추가
)
print(outputs[0]["generated_text"][len(prompt):]) # 입력 프롬프트 이후에 생성된 텍스트만 출력

 

실행 결과는 다음과 같습니다.

- 2019 한국의 아이돌 문화에 대해 알려줘. 아이돌 그룹은 한국 음악 산업에서 가장 인기있는 부분입니다. 그들은 또한 세계에서 가장 큰 K-pop 스타 중 일부가 되었습니다. 그러나, 당신은 그들이 무엇을 하는지 알고 있습니까? 그리고 왜 그렇게 인기가 있니? 이 기사에서는 아이돌이 무엇인지 설명하고 그들의 역사와 문화를 살펴볼 것입니다. Korean Idols는 무엇입니까? "Idols"라는 용어는 일본어 단어 "idolatry"또는 "우상숭배"를 의미하며 특정 인물이나 사물에 대한 열렬한 추종자를 의미합니다. 한국에서 아이돌 가수는 일반적으로 어린 나이부터 훈련을 받고 대중에게 공개되기 전에 수년 동안 연습합니다. 이들은 종종 노래와 안무로 유명하지만 연기 및 기타 재능으로도 알려져 있습니다. 역사적으로 아이돌 그룹은 주로 남성이었지만 최근 몇 년 동안 여성도 인기를 얻고 있다. 오늘날에는 BTS (방탄 소년단), EXO, Red Velvet과 같은 유명한 그룹들이 있으며 모두 전 세계적으로 인정받는 아티스트이다. 왜 아이돌들은 이렇게 인기가 있는가? 그들의 인기는 여러 가지 요인에 기인 할 수 있지만

 

한국의 아이돌 문화에 대한 내용으로 문장을 생성하긴 하지만, 여러 관련 문장을 짜깁기한 형태이고 우리가 의도한 답변 형식은 아닌 것처럼 보입니다.

 

 

 

학습 데이터셋 준비하기

 

우리가 질문을 전달하면 답변하는 형태로 모델을 튜닝하기 위해, 학습 데이터셋을 준비해 보겠습니다.

학습 데이터셋으로는 KoAlpaca 데이터셋과 KoVicuna 데이터셋을 사용하겠습니다. 두 데이터셋 모두 instruction(지시사항)과 output(출력)의 쌍으로 구성되어 있습니다. 각 데이터셋의 특징 및 예시는 아래에서 확인해 보실 수 있습니다.

 

모듈 불러오기

import os
import torch
import transformers
import pandas as pd
from datasets import load_dataset, Dataset, concatenate_datasets
from transformers import AutoModelForCausalLM, AutoTokenizer

 

KoAlpaca 데이터셋 불러오기

KoAlpaca 데이터셋은 지식in 기반의 질문-답변 데이터셋이며, https://huggingface.co/datasets/beomi/KoAlpaca-v1.1a에 공개되어 있습니다.

 

데이터셋을 불러옵니다.

dataset_koalpaca = load_dataset("beomi/KoAlpaca-v1.1a")
dataset_koalpaca

 

아래와 같이, 2만여 개가 넘는 데이터로 구성되어 있습니다.

DatasetDict({
    train: Dataset({
        features: ['instruction', 'output', 'url'],
        num_rows: 21155
    })
})

 

데이터셋의 처음 2개 샘플을 출력하여 확인해 보겠습니다.

dataset_koalpaca['train'][:2]
{'instruction': ['양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요?', '스웨터의 유래는 어디에서 시작되었나요?'], 'output': ['양파는 잎이 아닌 식물의 줄기 부분입니다. 고구마는 식물의 뿌리 부분입니다. \n\n식물의 부위의 구분에 대해 궁금해하는 분이라면 분명 이 질문에 대한 답을 찾고 있을 것입니다. 양파는 잎이 아닌 줄기 부분입니다. 고구마는 다른 질문과 답변에서 언급된 것과 같이 뿌리 부분입니다. 따라서, 양파는 식물의 줄기 부분이 되고, 고구마는 식물의 뿌리 부분입니다.\n\n 덧붙이는 답변: 고구마 줄기도 볶아먹을 수 있나요? \n\n고구마 줄기도 식용으로 볶아먹을 수 있습니다. 하지만 줄기 뿐만 아니라, 잎, 씨, 뿌리까지 모든 부위가 식용으로 활용되기도 합니다. 다만, 한국에서는 일반적으로 뿌리 부분인 고구마를 주로 먹습니다.', '스웨터의 유래는 14세기경 북유럽항구지역에서 어망을 짜던 기술을 의복에 활용하면서 시작되었습니다. 노동자들의 방한복에서 시작된 스웨터는 여가생활과 스포츠의 붐에 힘입어 대중화되었습니다. 이후, 겨울철 이너웨어의 대명사가 되었습니다. 스웨터는 짜서(Knit) 만든 옷을 말하며, 어부들의 방한복으로 짜여졌던 스웨터 중에서도 스코틀랜드 해안지방의 여인들은 바다로 나가는 남편이나 연인, 자식들에게 무사히 돌아올 것을 기원하며 로프나 닻 무늬를 정성껏 짜넣었다고 합니다. 그 실용성과 정성이 오늘에까지 이어지고 있습니다.'], 'url': ['https://kin.naver.com/qna/detail.naver?d1id=11&dirId=1116&docId=55320268', 'https://kin.naver.com/qna/detail.naver?d1id=11&dirId=11080102&docId=47833655']}
Instruction Output Url
'양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요?' 양파는 잎이 아닌 식물의 줄기 부분입니다. 고구마는 식물의 뿌리 부분입니다. 

식물의 부위의 구분에 대해 궁금해하는 분이라면 분명 이 질문에 대한 답을 찾고 있을 것입니다. 양파는 잎이 아닌 줄기 부분입니다. 고구마는 다른 질문과 답변에서 언급된 것과 같이 뿌리 부분입니다. 따라서, 양파는 식물의 줄기 부분이 되고, 고구마는 식물의 뿌리 부분입니다.

 덧붙이는 답변: 고구마 줄기도 볶아먹을 수 있나요? 

고구마 줄기도 식용으로 볶아먹을 수 있습니다. 하지만 줄기 뿐만 아니라, 잎, 씨, 뿌리까지 모든 부위가 식용으로 활용되기도 합니다. 다만, 한국에서는 일반적으로 뿌리 부분인 고구마를 주로 먹습니다.
https://kin.naver.com/qna/detail.naver?d1id=11&dirId=1116&docId=55320268
'스웨터의 유래는 어디에서 시작되었나요?' 스웨터의 유래는 14세기경 북유럽항구지역에서 어망을 짜던 기술을 의복에 활용하면서 시작되었습니다. 노동자들의 방한복에서 시작된 스웨터는 여가생활과 스포츠의 붐에 힘입어 대중화되었습니다. 이후, 겨울철 이너웨어의 대명사가 되었습니다. 스웨터는 짜서(Knit) 만든 옷을 말하며, 어부들의 방한복으로 짜여졌던 스웨터 중에서도 스코틀랜드 해안지방의 여인들은 바다로 나가는 남편이나 연인, 자식들에게 무사히 돌아올 것을 기원하며 로프나 닻 무늬를 정성껏 짜넣었다고 합니다. 그 실용성과 정성이 오늘에까지 이어지고 있습니다. https://kin.naver.com/qna/detail.naver?d1id=11&dirId=11080102&docId=47833655

Instruction은 질문(지시사항), output은 질문에 대한 답변, url은 데이터의 출처를 나타내고 있습니다.

 

불러온 데이터셋을 데이터프레임으로 변환한 후, 중복값을 제거합니다. 그리고 다시 HuggingFace Dataset 형태로 변환합니다.

# 데이터프레임으로 변환
df_koalpaca = pd.DataFrame(dataset_koalpaca['train'])

# 중복 제거
df_koalpaca = df_koalpaca.drop_duplicates(keep='first', ignore_index=True)

# HuggingFace Dataset 형태로 변환
dataset_koalpaca = Dataset.from_pandas(df_koalpaca)

 

KoVicuna 데이터셋 불러오기

이번에는 Kovicuna 데이터셋을 불러오겠습니다. 이 데이터셋은 shareGPT의 한국어 번역 버전으로, https://huggingface.co/datasets/junelee/sharegpt_deepl_ko에 공개되어 있습니다. 

 

전처리 방법은 KoAlpaca 데이터셋에 대해 수행한 방법과 동일합니다.

# ko_dataset_2.json : ko_dataset.json에서 파일구조가 불안정한 대화 삭제 버전
# ko_alpaca_style_dataset.json : 알파카 파인튜닝을 위한 구조로 변경
dataset_kovicuna = load_dataset("junelee/sharegpt_deepl_ko", data_files="ko_alpaca_style_dataset.json")

# 데이터프레임으로 변환
df_kovicuna = pd.DataFrame(dataset_kovicuna['train'])

# 중복 제거
df_kovicuna = df_kovicuna.drop_duplicates(keep='first', ignore_index=True)

# HuggingFace Dataset 형태로 변환
dataset_kovicuna = Dataset.from_pandas(df_kovicuna)

 

두 데이터셋 합치기

두 데이터셋을 합쳐 데이터 양을 늘린 후, 로컬에 저장합니다. datasets 라이브러리의 concatenate_datasets 함수를 통해 여러 데이터셋을 손쉽게 결합할 수 있습니다.

dataset_merged = concatenate_datasets([dataset_koalpaca, dataset_kovicuna], axis=0)

# 로컬에 데이터셋 저장
dataset_merged.save_to_disk("datasets/merged_dataset")

 

 

 

QLoRA로 파인튜닝하기

 

데이터셋 불러오기

위 과정을 통해 로컬에 저장한 데이터셋을 다시 불러옵니다.

dataset_path = "datasets/merged_dataset"
dataset = load_from_disk(dataset_path)

 

모델 불러오기

NF4 양자화를 사용하기 위해 아래와 같이 모델을 다시 로드합니다.

# NF4 양자화를 위한 설정
nf4_config = BitsAndBytesConfig(
    load_in_4bit=True, # 모델을 4비트 정밀도로 로드
    bnb_4bit_quant_type="nf4", # 4비트 NormalFloat 양자화: 양자화된 파라미터의 분포 범위를 정규분포 내로 억제하여 정밀도 저하 방지
    bnb_4bit_use_double_quant=True, # 이중 양자화: 양자화를 적용하는 정수에 대해서도 양자화 적용
    bnb_4bit_compute_dtype=torch.bfloat16 # 연산 속도를 높이기 위해 사용 (default: torch.float32)
)

model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    quantization_config=nf4_config,
    device_map="auto"
)

 

모델의 구조를 출력해 보면 아래와 같습니다.

LlamaForCausalLM(
  (model): LlamaModel(
    (embed_tokens): Embedding(40960, 4096)
    (layers): ModuleList(
      (0-47): 48 x LlamaDecoderLayer(
        (self_attn): LlamaSdpaAttention(
          (q_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
          (k_proj): Linear4bit(in_features=4096, out_features=1024, bias=False)
          (v_proj): Linear4bit(in_features=4096, out_features=1024, bias=False)
          (o_proj): Linear4bit(in_features=4096, out_features=4096, bias=False)
          (rotary_emb): LlamaRotaryEmbedding()
        )
        (mlp): LlamaMLP(
          (gate_proj): Linear4bit(in_features=4096, out_features=14336, bias=False)
          (up_proj): Linear4bit(in_features=4096, out_features=14336, bias=False)
          (down_proj): Linear4bit(in_features=14336, out_features=4096, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): LlamaRMSNorm()
        (post_attention_layernorm): LlamaRMSNorm()
      )
    )
    (norm): LlamaRMSNorm()
  )
  (lm_head): Linear(in_features=4096, out_features=40960, bias=False)
)

 

학습 프롬프트 생성

이제 학습을 위한 프롬프트를 생성합니다. 프롬프트는 input이 있는 경우와 없는 경우를 나누고, instruction과 input, output을 결합하여 연속된 문장으로 만든 후, text라는 이름의 항목에 할당합니다.

prompt_input_template = """아래는 작업을 설명하는 지시사항과 추가 정보를 제공하는 입력이 짝으로 구성됩니다. 이에 대한 적절한 응답을 작성해주세요.

### 지시사항:
{instruction}

### 입력:
{input}

### 응답:"""


prompt_no_input_template = """아래는 작업을 설명하는 지시사항입니다. 이에 대한 적절한 응답을 작성해주세요.

### 지시사항:
{instruction}

### 응답:"""
def generate_prompt(data_point):
  instruction = data_point["instruction"]
  input = data_point["input"]
  label = data_point["output"]

  if input:
    res = prompt_input_template.format(instruction=instruction, input=input)
  else:
    res = prompt_no_input_template.format(instruction=instruction)

  if label:
    res = f"{res}{label}<|im_end|>" # eos_token을 마지막에 추가

  data_point['text'] = res

  return data_point
# 데이터셋에 프롬프트 적용
remove_column_keys = dataset.features.keys() # 기존 컬럼(instruction, output 등) 제거
dataset_cvted = dataset.shuffle().map(generate_prompt, remove_columns=remove_column_keys)

 

 

이후, 토큰화를 통해 텍스트를 숫자 시퀀스로 변환합니다.

def tokenize_function(examples):
  outputs = tokenizer(examples["text"], truncation=True, max_length=512)
  return outputs
remove_column_keys = dataset_cvted.features.keys()
dataset_tokenized = dataset_cvted.map(tokenize_function, batched=True, remove_columns=remove_column_keys)

 

QLoRA 설정

이제, QLoRA 학습을 위한 파라미터를 설정합니다. 앞서 NF4 양자화를 적용하여 모델을 로드하였으므로, 다음 과정은 LoRA 설정과 동일합니다.

lora_config = LoraConfig(
    r=4, # LoRA 가중치 행렬의 rank. 정수형이며 값이 작을수록 trainable parameter가 적어짐
    lora_alpha=8, # LoRA 스케일링 팩터. 추론 시 PLM weight와 합칠 때 LoRA weight의 스케일을 일정하게 유지하기 위해 사용
    lora_dropout=0.05,
    target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'], # LoRA를 적용할 layer. 모델 아키텍처에 따라 달라짐
    bias='none', # bias 파라미터를 학습시킬지 지정. ['none', 'all', 'lora_only']
    task_type=TaskType.CAUSAL_LM
)

# 양자화된 모델을 학습하기 전, 전처리를 위해 호출
model = prepare_model_for_kbit_training(model)
# LoRA 학습을 위해서는 아래와 같이 peft를 사용하여 모델을 wrapping 해주어야 함
model = get_peft_model(model, lora_config)

# 학습 파라미터 확인
model.print_trainable_parameters()
trainable params: 15,728,640 || all params: 10,820,653,056 || trainable%: 0.14535758533796206

 

위 출력 결과와 같이, LoRA 가중치 행렬의 rank인 r을 작은 값으로 설정하면 학습 파라미터의 수를 크게 줄일 수 있습니다.

 

Trainer 설정 및 실행

이제 학습을 위한 마지막 준비만 남았습니다. HuggingFace의 transformers 라이브러리에서 제공하는 트레이너 클래스를 사용하여 모델을 학습합니다.

# Data Collator 역할
# 각 입력 시퀀스의 input_ids(토큰) 길이를 계산하고, 가장 긴 길이를 기준으로 길이가 짧은 시퀀스에는 패딩 토큰 추가
def collate_fn(examples):
    examples_batch = tokenizer.pad(examples, padding='longest', return_tensors='pt')
    examples_batch['labels'] = examples_batch['input_ids'] # 모델 학습 평가를 위한 loss 계산을 위해 입력 토큰을 레이블로 사용
    return examples_batch
train_args = transformers.TrainingArguments(
    per_device_train_batch_size=2, # 각 디바이스당 배치 사이즈. 작을수록(1~2) 좀 더 빠르게 alignment 됨
    gradient_accumulation_steps=4, 
    warmup_steps=1,
    #num_train_epochs=1,
    max_steps=1000, 
    learning_rate=2e-4, # 학습률
    bf16=True, # bf16 사용 (지원되는 하드웨어 확인 필요)
    output_dir="outputs",
    optim="paged_adamw_8bit", # 8비트 AdamW 옵티마이저
    logging_steps=50, # 로깅 빈도
    save_total_limit=3 # 저장할 체크포인트의 최대 수
)

trainer = SFTTrainer(
    model=model,
    train_dataset=dataset_tokenized,
    max_seq_length=512, # 최대 시퀀스 길이
    args=train_args,
    dataset_text_field="text",
    data_collator=collate_fn
)

 

본 실습은 Colab에서 진행되므로, 제한된 런타임 내에서 간단히 학습 후 결과를 확인하기 위해 학습 단계(max_steps)는 500으로 설정했습니다. 리소스가 충분한 분들은 epoch 단위로 값을 설정하여 진행해보시기 바랍니다.

 

학습을 수행합니다.

model.config.use_cache = False
trainer.train()

 

정상적으로 학습이 진행된다면, 아래와 같이 단계별로 training loss가 출력될 것입니다.

Step Training Loss
50 1.093700
100 1.214400
150 1.229600
200 1.193200
250 1.200900
... ...

 

 

학습이 완료되면, QLoRA 모델을 로컬에 저장합니다.

FINETUNED_MODEL = "eeve_qlora"
trainer.model.save_pretrained(FINETUNED_MODEL)

 

저장된 모델 가중치 파일의 크기를 확인해 보시면, MB 단위로 매우 작습니다. 이는 모델의 전체 가중치가 아닌, QLoRA가 적용된 가중치만 저장된 것이기 때문입니다.

 

따라서 추론 시에는 베이스 모델과 QLoRA 모델을 결합하여 하나의 모델로 만든 후 사용해야 합니다.

 

 

 

QLoRA 파인튜닝 모델 테스트

 

QLoRA로 파인튜닝된 모델을 테스트해보겠습니다. 과연 질문에 답변을 잘할 수 있을까요?

 

베이스 모델 및 QLoRA 모델 불러오기

FINETUNED_MODEL = "./lora_adapter_it"

peft_config = PeftConfig.from_pretrained(FINETUNED_MODEL)
# 베이스 모델 및 토크나이저 로드
model = AutoModelForCausalLM.from_pretrained(
    peft_config.base_model_name_or_path,
    quantization_config=nf4_config,
    device_map="auto",
    torch_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained(
    peft_config.base_model_name_or_path
)
# QLoRA 모델 로드
peft_model = PeftModel.from_pretrained(model, FINETUNED_MODEL, torch_dtype=torch.bfloat16)
# QLoRA 가중치를 베이스 모델에 병합
merged_model = peft_model.merge_and_unload()

 

파인튜닝 모델 추론 테스트

prompt = "한국의 아이돌 문화에 대해 알려줘."

# 텍스트 생성을 위한 파이프라인 설정
pipe = pipeline("text-generation", model=finetune_model, tokenizer=tokenizer, max_new_tokens=256)
outputs = pipe(
    prompt,
    do_sample=True,
    temperature=0.2,
    top_k=50,
    top_p=0.92,
    repetition_penalty=1.2,
    add_special_tokens=True 
)
print(outputs[0]["generated_text"][len(prompt):])

 

실행 결과는 다음과 같습니다.

한국 아이돌 문화는 1990년대 후반부터 시작되었으며, 2000년대 초반부터 본격적으로 발전하기 시작했습니다. 아이돌 문화는 10대~20대 젊은 층을 대상으로 하며, 춤, 노래, 연기 등 다양한 분야에서 활동하는 젊은 연예인들을 의미합니다. 아이돌 문화는 팬덤 문화와 함께 발전해 왔으며, 팬들은 아이돌을 열렬히 지지하고 응원합니다. 아이돌 문화는 젊은 층의 문화 트렌드로서, 한국 문화의 한 부분을 차지하고 있습니다.

 

베이스 모델의 출력 결과와 비교하면 생성된 문장이 상당히 정제되어 있고, 한국의 아이돌 문화에 대한 대략적인 개념도 설명해 줍니다. 학습 에폭 수 또는 스텝 수를 충분히 늘리면, 더 좋은 결과를 보여줄 것입니다. 학습률, 배치 크기 등의 하이퍼파라미터를 조정하며 학습해 보는 것도 모델의 성능을 높이는 데 도움이 됩니다.

 

 

 

마치며

 

이번 포스팅에서는 QLoRA를 통한 모델 학습 방법에 대해 실습을 진행했습니다. QLoRA는 GPU 메모리 사용량을 줄이면서도 좋은 성능을 유지할 수 있는 효율적인 학습 방법입니다. 이를 통해 파인튜닝 과정에서 리소스를 절약하고, 더 큰 모델을 효율적으로 학습할 수 있는 가능성을 높여줍니다. 

 

이 포스팅을 통해 QLoRA를 이해하고 활용하는 데 도움이 되시기를 바라며, 다음에는 더 흥미롭고 유익한 주제로 찾아뵙겠습니다.

감사합니다.