들어가며
7B, 13B, 70B 파라미터의 LLM을 처음부터 학습하는 것은 수십~수백 개의 GPU와 수백만 달러가 필요합니다. 하지만 **파인튜닝**을 활용하면 소비자급 GPU 1장으로도 나만의 특화 모델을 만들 수 있습니다.
이 글에서는 **LoRA**, **QLoRA**, **PEFT** 라이브러리를 활용한 실전 파인튜닝 방법을 다룹니다.
Full Fine-tuning vs Parameter-Efficient Fine-tuning
Full Fine-tuning의 문제
7B 모델을 Full Fine-tuning하려면:
- **모델 파라미터**: 7B × 4 bytes (FP32) = **28GB**
- **Optimizer 상태**: Adam은 파라미터의 2배 = **56GB**
- **Gradient**: 파라미터와 동일 = **28GB**
- **총 VRAM**: 약 **112GB** 이상 필요
→ A100 80GB 1장으로도 부족합니다.
PEFT의 등장
Parameter-Efficient Fine-tuning(PEFT)는 전체 파라미터의 **0.1~1%만** 학습합니다:
| 방법 | 학습 파라미터 비율 | VRAM (7B 기준) |
|---|---|---|
| Full Fine-tuning | 100% | ~112GB |
| LoRA | ~0.1-1% | ~16GB |
| QLoRA | ~0.1-1% | ~6GB |
LoRA: Low-Rank Adaptation
수학적 원리
LoRA의 핵심 아이디어: **가중치 업데이트 행렬 ΔW는 저랭크(low-rank)다.**
기존 선형 변환:
$$y = Wx$$
LoRA 적용:
$$y = Wx + \frac{\alpha}{r} \cdot BAx$$
여기서:
- $W \in \mathbb{R}^{d \times d}$: 원본 가중치 (freeze)
- $B \in \mathbb{R}^{d \times r}$: 저랭크 행렬 (학습)
- $A \in \mathbb{R}^{r \times d}$: 저랭크 행렬 (학습)
- $r$: 랭크 (보통 4~64, 원본 차원 대비 매우 작음)
- $\alpha$: 스케일링 팩터
원본 W (4096 × 4096) = 16M 파라미터 [freeze]
LoRA:
A (r × 4096) + B (4096 × r) = r × 8192 파라미터
r=8일 경우: 65,536 파라미터 (0.4%)
코드로 구현하기
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM, AutoTokenizer
1. 기본 모델 로드
model_name = "meta-llama/Llama-3.1-8B"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
2. LoRA 설정
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # 랭크
lora_alpha=32, # 스케일링 (보통 r의 2배)
lora_dropout=0.05, # 드롭아웃
target_modules=[ # LoRA를 적용할 모듈
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
bias="none"
)
3. PEFT 모델 생성
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
trainable params: 41,943,040 || all params: 8,072,204,288 || trainable%: 0.5194
target_modules 선택 가이드
모델의 모든 Linear 레이어 확인
for name, module in model.named_modules():
if isinstance(module, torch.nn.Linear):
print(name, module.in_features, module.out_features)
일반적인 선택:
- Attention만: ["q_proj", "v_proj"] — 최소 VRAM
- Attention 전체: ["q_proj", "k_proj", "v_proj", "o_proj"] — 권장
- MLP 포함: 위 + ["gate_proj", "up_proj", "down_proj"] — 최대 성능
QLoRA: 4-bit 양자화 + LoRA
QLoRA가 특별한 이유
QLoRA는 3가지 혁신으로 **소비자 GPU에서 대형 모델 파인튜닝**을 가능하게 합니다:
1. **4-bit NormalFloat(NF4)**: 정규분포 가중치에 최적화된 양자화
2. **Double Quantization**: 양자화 상수 자체도 양자화하여 메모리 추가 절감
3. **Paged Optimizers**: GPU 메모리 부족 시 CPU로 자동 페이징
구현
from transformers import BitsAndBytesConfig
4-bit 양자화 설정
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4
bnb_4bit_compute_dtype=torch.bfloat16, # 연산은 bf16으로
bnb_4bit_use_double_quant=True, # Double Quantization
)
양자화된 모델 로드
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
quantization_config=bnb_config,
device_map="auto"
)
LoRA 적용 (QLoRA = 4bit 양자화 모델 + LoRA)
model = get_peft_model(model, lora_config)
VRAM 사용량 비교
| 모델 | Full FP16 | LoRA FP16 | QLoRA 4-bit |
|---|---|---|---|
| 7B | ~28GB | ~16GB | ~6GB |
| 13B | ~52GB | ~30GB | ~10GB |
| 70B | ~280GB | ~160GB | ~48GB |
QLoRA를 사용하면 **RTX 3090/4090 (24GB)** 하나로 13B 모델까지 파인튜닝할 수 있습니다.
데이터 준비와 학습
데이터셋 포맷
Instruction 파인튜닝을 위한 데이터 포맷:
from datasets import load_dataset
Alpaca 스타일 데이터셋
dataset = load_dataset("json", data_files="train_data.json")
데이터 예시
{
"instruction": "다음 텍스트를 요약해주세요.",
"input": "Kubernetes는 컨테이너화된 워크로드와 서비스를...",
"output": "Kubernetes는 컨테이너 오케스트레이션 플랫폼입니다."
}
프롬프트 템플릿 적용
def format_instruction(sample):
if sample["input"]:
text = f"""### Instruction:
{sample["instruction"]}
Input:
{sample["input"]}
Response:
{sample["output"]}"""
else:
text = f"""### Instruction:
{sample["instruction"]}
Response:
{sample["output"]}"""
return {"text": text}
dataset = dataset.map(format_instruction)
SFTTrainer로 학습
from trl import SFTTrainer, SFTConfig
training_args = SFTConfig(
output_dir="./output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 효과적 배치 = 4 × 4 = 16
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.03,
max_seq_length=2048,
bf16=True,
logging_steps=10,
save_strategy="epoch",
optim="paged_adamw_8bit", # QLoRA용 페이징 옵티마이저
gradient_checkpointing=True, # VRAM 추가 절감
)
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset["train"],
args=training_args,
)
trainer.train()
학습 후 모델 저장 및 병합
LoRA 어댑터만 저장 (수십 MB)
model.save_pretrained("./lora-adapter")
나중에 어댑터 로드
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B")
model = PeftModel.from_pretrained(base_model, "./lora-adapter")
원본 모델과 어댑터 병합 (배포용)
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./merged-model")
하이퍼파라미터 튜닝 가이드
LoRA 랭크(r) 선택
랭크별 특성
r=4: 가장 적은 파라미터, 간단한 도메인 적응에 적합
r=8: 일반적인 시작점
r=16: 좋은 균형 (추천)
r=32: 복잡한 태스크, 더 많은 VRAM
r=64+: Full Fine-tuning에 가까운 성능, 그만큼 비효율
실험적으로, r=16 + alpha=32가 대부분의 경우에 잘 작동
학습률
LoRA/QLoRA 학습률은 Full FT보다 높게 설정
Full FT: 1e-5 ~ 5e-5
LoRA: 1e-4 ~ 3e-4
QLoRA: 2e-4 (일반적)
고급 기법
DoRA: Weight-Decomposed Low-Rank Adaptation
LoRA의 발전형으로, 가중치를 크기(magnitude)와 방향(direction)으로 분해합니다:
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
use_dora=True, # DoRA 활성화
)
여러 LoRA 어댑터 조합
from peft import PeftModel
기본 모델에 여러 어댑터 로드
model = PeftModel.from_pretrained(base_model, "./adapter-korean")
model.load_adapter("./adapter-code", adapter_name="code")
어댑터 전환
model.set_adapter("code")
또는 어댑터 가중치 조합
model.add_weighted_adapter(
adapters=["default", "code"],
weights=[0.7, 0.3],
adapter_name="merged"
)
마무리
LoRA와 QLoRA는 LLM 파인튜닝의 접근성을 혁명적으로 바꿨습니다. 소비자 GPU 1장으로 수십억 파라미터 모델을 커스터마이징할 수 있다는 것은 AI 민주화의 핵심입니다.
핵심 요약:
- **LoRA**: 저랭크 분해로 학습 파라미터를 0.1~1%로 줄임
- **QLoRA**: 4-bit 양자화로 VRAM을 추가로 ~4배 절감
- **PEFT**: Hugging Face 라이브러리로 몇 줄의 코드로 적용 가능
직접 데이터를 준비하고, 학습하고, 배포해보세요. 생각보다 훨씬 쉽습니다.
퀴즈
가중치 업데이트 행렬 ΔW를 분해할 때의 저차원 크기. r이 작을수록 학습 파라미터가 적고 VRAM 소비가 줄지만, 모델의 표현력도 제한됩니다.
약 112GB 이상. 모델 파라미터(28GB) + Optimizer 상태(56GB) + Gradient(28GB)이 필요합니다.
1) 4-bit NormalFloat(NF4) 양자화, 2) Double Quantization(양자화 상수의 양자화), 3) Paged Optimizers(GPU→CPU 자동 페이징)
LoRA 업데이트의 스케일링 팩터. 실제 스케일은 alpha/r로 계산되며, 보통 r의 2배로 설정합니다 (r=16이면 alpha=32).
신경망 가중치가 대략 정규분포를 따르므로, 정규분포에 최적화된 NF4 양자화가 균일 분포를 가정하는 INT4보다 정보 손실이 적습니다.
추론 시 추가 연산 오버헤드를 없애기 위해. 병합하면 원본 모델과 동일한 구조가 되어 추론 속도가 어댑터 분리 상태보다 빠릅니다.
순전파의 중간 활성값을 메모리에 저장하지 않고, 역전파에서 재계산합니다. VRAM을 절약하지만 학습 시간은 약 20-30% 증가합니다.
더 많은 모듈에 LoRA를 적용할수록 성능은 향상되지만 VRAM과 학습 시간이 증가합니다. Attention의 q_proj, v_proj만 적용하는 것이 최소 설정이고, MLP까지 포함하면 최대 성능을 얻을 수 있습니다.
현재 단락 (1/151)
7B, 13B, 70B 파라미터의 LLM을 처음부터 학습하는 것은 수십~수백 개의 GPU와 수백만 달러가 필요합니다. 하지만 **파인튜닝**을 활용하면 소비자급 GPU 1장으로도 ...