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-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는 메모리와 연산 자원을 절약하면서도 높은 성능을 유지할 수 있음을 실제 테스트를 통해 알 수 있었다.