KoAlpaca 데이터셋을 활용한 Llama 3.1 LoRA 기반 PEFT

Llama 3.1 모델을 LoRA(Low-Rank Adaptation) 기법을 사용해 한국어 데이터셋인 KoAlpaca를 기반으로 Parameter Efficient Fine-Tuning 하는 과정을 다룬다.

LoRA는 대규모 언어 모델을 적은 자원으로도 효율적으로 학습할 수 있도록 돕는 PEFT의 한 기법이다.


 

1. 환경 설정

먼저, 모델을 학습하거나 미세 조정하기 위해 필요한 라이브러리를 설치한다.

!pip install accelerate  # 모델 학습 속도 향상
!pip install peft        # LoRA를 포함한 효율적 미세 조정
!pip install bitsandbytes # 양자화 기술을 통한 메모리 최적화
!pip install transformers # 트랜스포머 기반 모델 라이브러리
!pip install datasets     # 데이터셋 로드 및 처리
!pip install trl          # 트랜스포머 기반 강화 학습
!pip install pandas       # 데이터 처리 라이브러리

 

2. Hugging Face 로그인

Hugging Face Model Hub에서 meta-llama/Llama-3.1-8B-Instruct 모델을 다운로드하려면

Hugging Face 계정을 통해 Meta의 인증을 받고 token을 사용해야한다.

 

https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct

 

Huggingface API 키 발급 방법

 

huggingface-cli login 명령어를 사용해 Hugging Face에 로그인하고 API 토큰을 사용해 인증을 진행한다.

!huggingface-cli login --token ${HF_TOKEN}

 

3. 데이터셋 불러오기 및 전처리

pandas를 이용해 JSON 형식의 데이터셋을 불러오고 데이터를 확인한다.
한국어 데이터셋인 beomi/KoAlpaca-v1.1a의 100행만 추출하여 KoAlpaca_100.json 파일로 만들어서 사용했다.

import pandas as pd

# JSON 형식의 데이터셋을 판다스로 불러옴
df = pd.read_json("KoAlpaca_100.json")

# 데이터 확인
df.head(5)
  instruction output
0 양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요? 양파는 잎이 아닌 식물의 줄기 부분입니다. 고구마는 식물의 뿌리 부분입니다. \n\...
1 스웨터의 유래는 어디에서 시작되었나요? 스웨터의 유래는 14세기경 북유럽항구지역에서 어망을 짜던 기술을 의복에 활용하면서 ...
2 토성의 고리가 빛의 띠로 보이는 이유는 무엇인가요? \n\n토성의 고리는 얼음과 ... 토성의 고리가 미세한 입자들로 이루어져 있기 때문에, 입자들의 밀도 차이 때문에 카...
3 화장품 OEM과 화장품 ODM의 차이점은 무엇인가요?\n화장품 자체 제조 브랜드 런... 화장품 제조업체는 대체로 OEM과 ODM을 통해 제품을 만듭니다. OEM은 브랜드에...
4 '사이보그'는 언제 처음 등장한 말이며, 그 의미와 종류에는 어떤 것이 있는지 알고... '사이보그'는 1960년에 처음 등장한 말로, 기계와 유기체가 합성되어 생겨난 새로...

 

4. 데이터셋 포맷팅

Llama3.1에서 지원하는 Special Tokens을 활용한 Instruct 구조에 맞게 데이터 형식을 수정한다.

참고 : https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_1/#prompt-template

각 데이터는 'user'(사용자)와 'assistant'(모델 응답)의 대화 형식으로 구성한다.

from datasets import Dataset

def format_example(row):
    return {
        'text': f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>

        You are a helpful assistant<|eot_id|>\n<|start_header_id|>user<|end_header_id|>

        {row['instruction']}<|eot_id|>\n<|start_header_id|>assistant<|end_header_id|>

        {row['output']}<|eot_id|>"""
    }

# 판다스 데이터프레임을 데이터셋으로 변환하고, 포맷팅 함수 적용
dataset = Dataset.from_pandas(df)
dataset = dataset.map(format_example)

 

5. 모델 및 토크나이저 로드

LoRA를 사용해 모델을 메모리 효율적으로 로드하고, 토크나이저를 설정한다.

from transformers import AutoModelForCausalLM

base_model = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    base_model,
    load_in_8bit=True,  # 8비트 양자화로 메모리 절감
    device_map="auto"
)

# 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(
              base_model,
              trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token  # 시퀀스 패딩에 eos 토큰 사용
tokenizer.padding_side = "right"           # 패딩을 오른쪽에 추가

 

6. LoRA 설정

LoRA 설정을 통해 모델의 특정 파라미터만 조정하여 미세 조정(PEFT)을 수행한다.

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

# LoRA 설정
peft_params = LoraConfig(
    lora_alpha=16,       # LoRA의 스케일링 계수 설정
    lora_dropout=0.1,    # 드롭아웃을 통해 과적합 방지
    r=8,                 # LoRA 어댑터 행렬의 Rank 설정
    bias="none",         # 편향 사용 여부 설정
    task_type="CAUSAL_LM", # 작업 유형 설정 (Causal LM)
    target_modules=['k_proj', 'q_proj', 'v_proj', 'o_proj'] # 적용 모듈 설정
)

# 모델을 8bit 학습을 위한 상태로 준비. 메모리를 절약하면서도 모델의 성능을 유지할 수 있음
model = prepare_model_for_kbit_training(model, 8)

# PEFT 어댑터 설정을 모델에 적용
model = get_peft_model(model, peft_params)
  • lora_alpha : 학습 속도를 조절하는 계수
  • lora_dropout : 과적합을 방지하기 위한 드롭아웃 확률
  • r : 어댑터 행렬의 Rank를 설정
  • target_modules : 미세 조정할 모듈을 지정

 

7. Training Parameter 설정 및 학습

학습 파라미터를 설정하고 모델 학습을 시작한다.

from trl import SFTTrainer

# 학습 파라미터 설정
training_params = TrainingArguments(
    output_dir="./results",              # 결과 저장 경로
    num_train_epochs=50,                 # 학습 에폭 수
    per_device_train_batch_size=8,       # 배치 사이즈
    learning_rate=2e-4,                  # 학습률 설정
    save_steps=1000,                     # 저장 빈도
    logging_steps=50,                    # 로그 출력 빈도
    fp16=True                            # 16-bit 부동 소수점 사용 (메모리 절약)
)

# SFTTrainer를 사용해 학습 실행
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    peft_config=peft_params,
    dataset_text_field="text",
    max_seq_length=None,  # 시퀀스 길이 제한
    tokenizer=tokenizer,
    args=training_params,
)

trainer.train()

 

8. 모델 테스트

모델을 미세 조정한 후, 학습된 모델이 잘 작동하는지 검증하기 위해 테스트를 진행한다.
테스트는 transformers 라이브러리의 pipeline을 사용해 진행한다.

from transformers import pipeline

pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer, max_length=256)

def generate_and_stop(prompt):
    result = pipe(f"{prompt}")[0]['generated_text']
    return result

prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>

You are a helpful assistant<|eot_id|>\n<|start_header_id|>user<|end_header_id|>

양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요?<|eot_id|>\n<|start_header_id|>assistant<|end_header_id|>
"""

generate_and_stop(prompt)

 

테스트 결과

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

You are a helpful assistant<|eot_id|>\n<|start_header_id|>user<|end_header_id|>

양파는 어떤 식물 부위인가요? 그리고 고구마는 뿌리인가요?<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
양파는 잎이 아닌 식물의 줄기 부분입니다. 고구마는 식물의 뿌리 부분입니다.

 

9. 결론

이번 글에서는 Llama 3.1 모델을 LoRA(Low-Rank Adaptation) 기법을 사용해 KoAlpaca 한국어 데이터셋으로 효율적으로 미세 조정하는 과정을 다루었다.
PEFT(Parameter Efficient Fine-Tuning) 기법은 대규모 언어 모델을 적은 자원으로도 학습할 수 있도록 해주며, 특히 LoRA는 메모리와 연산 자원을 절약하면서도 높은 성능을 유지할 수 있음을 실제 테스트를 통해 알 수 있었다.