LoRA와 QLoRA로 첫 파인튜닝하기
첫 학습의 목표
이번 장의 목표는 거창하지 않습니다. 한 장의 GPU에서 원본 모델 전체를 다시 학습하지 않고, 작은 LoRA 어댑터만 학습해 모델의 응답 습관을 바꾸는 것입니다. QLoRA는 여기에 4비트 로딩을 더해 VRAM 사용량을 줄입니다. 즉, load_in_4bit=True로 모델을 가볍게 불러오고, 필요한 부분에만 LoRA를 붙여 학습합니다.
Unsloth는 이런 경량 학습 흐름을 빠르고 적은 메모리로 실행하도록 돕습니다. 공식 저장소에서도 LoRA, 4비트, 16비트 학습과 로컬 실행·내보내기 흐름을 주요 기능으로 설명합니다.1
AI 생성 이미지
데이터 형식 먼저 맞추기
TRL의 SFTTrainer는 대표적으로 세 가지 형식을 받습니다.2
{"text": "### 질문n환불 정책을 알려주세요.nn### 답변n구매 후 7일 이내 환불 가능합니다."}{"messages": [{"role": "user", "content": "환불 정책은?"}, {"role": "assistant", "content": "구매 후 7일 이내 환불 가능합니다."}]}{"prompt": "환불 정책은?", "completion": "구매 후 7일 이내 환불 가능합니다."}첫 실습은 가장 단순한 {"text": ...} 형식이 좋습니다. 대화형 챗봇을 만들 때는 messages 형식이 자연스럽습니다. 이 경우 SFTTrainer가 채팅 템플릿을 적용할 수 있습니다. 형식이 애매하면 먼저 전처리해서 위 셋 중 하나로 맞추세요.
LoRA 설정의 의미
LoRA 설정은 학습 범위를 정합니다.
r: LoRA rank입니다. 값이 클수록 더 많은 패턴을 배울 수 있지만 VRAM과 학습 시간이 늘어납니다. 처음은8또는16이 무난합니다.lora_alpha: LoRA 변화량의 영향력을 조절합니다. 보통r과 같거나 2배 정도에서 시작합니다.lora_dropout: 학습 중 일부 연결을 끄는 비율입니다. 작은 데이터에서는0또는 낮은 값으로 시작하는 편이 단순합니다.target_modules: LoRA를 붙일 모델 내부 모듈입니다. 일반적으로 attention의q_proj,k_proj,v_proj,o_proj와 MLP의gate_proj,up_proj,down_proj를 많이 사용합니다.
Unsloth로 모델에 LoRA 붙이기
from unsloth import FastLanguageModel
from datasets import load_dataset
from trl import SFTTrainer, SFTConfig
max_seq_length = 2048
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
max_seq_length=max_seq_length,
load_in_4bit=True,
)
model = FastLanguageModel.get_peft_model(
model,
r=16,
lora_alpha=16,
lora_dropout=0,
bias="none",
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
use_gradient_checkpointing="unsloth",
random_state=3407,
)여기까지가 QLoRA의 핵심입니다. 원본 모델은 4비트로 불러오고, 실제 학습은 LoRA 어댑터에 집중합니다.
SFTTrainer로 학습 실행하기
dataset = load_dataset(
"json",
data_files={"train": "train.jsonl", "validation": "valid.jsonl"},
)
config = SFTConfig(
output_dir="outputs",
dataset_text_field="text",
max_seq_length=max_seq_length,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
warmup_steps=10,
num_train_epochs=1,
logging_steps=10,
eval_strategy="steps",
eval_steps=50,
save_steps=50,
packing=False,
)
trainer = SFTTrainer(
model=model,
processing_class=tokenizer,
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
args=config,
)
trainer.train()한 장의 GPU에서는 per_device_train_batch_size를 작게 두고 gradient_accumulation_steps를 키우는 방식이 안전합니다. 예를 들어 2 × 8 = 16처럼 누적 배치 크기를 맞추면, 메모리는 적게 쓰면서 비교적 안정적인 업데이트를 만들 수 있습니다. OOM이 나면 먼저 배치 크기, max_seq_length, 모델 크기를 줄이세요.
로그를 보고 멈출지 판단하기
학습 중 loss가 내려간다는 것은 모델이 학습 데이터의 다음 토큰을 더 잘 맞추고 있다는 뜻입니다. 하지만 그것만으로 좋은 모델이라고 판단하면 안 됩니다. 검증 loss가 오르는데 학습 loss만 계속 내려가면 과적합일 수 있습니다. 대표 질문을 몇 개 고정해 두고, 학습 전후 응답을 함께 비교하세요.
처음에는 num_train_epochs=1, 작은 데이터, 짧은 저장 간격으로 시작합니다. 품질이 나아지면 학습을 조금 늘리고, 답변이 외운 듯 반복되면 데이터를 줄이거나 정제합니다.
학습이 끝나면 어댑터를 저장합니다.
model.save_pretrained("my_lora_adapter")
tokenizer.save_pretrained("my_lora_adapter")이 폴더는 전체 모델이 아니라 LoRA 어댑터입니다. 실행할 때는 같은 base model이 필요합니다. 다음 단계에서는 이 어댑터를 다시 불러와 평가하고, 필요하면 병합해 배포용 모델로 내보냅니다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.