🔌

LoRA — 66M개를 전부 바꾸지 말고, 1M개짜리 어댑터만 끼우자

파인튜닝의 효율적 대안 — 베이스 모델은 고정하고, 작은 행렬 2개만 훈련한다

파인튜닝의 문제

파인튜닝은 66M개 숫자를 전부 조정한다. 이게 문제가 되는 경우:

  • 7B 모델(70억 개 숫자)을 파인튜닝하려면 GPU 메모리가 수십 GB 필요

  • 용도별로 모델을 5개 만들면 저장 공간이 5배

  • 훈련 시간도 오래 걸림

LoRA의 아이디어

"66M개 전부 바꿀 필요 없다. 작은 어댑터만 끼우면 된다."

파인튜닝:
  DistilBERT 66M개 전부 조정 → 66M개 저장 (260MB)

LoRA:
  DistilBERT 66M개 고정(얼림) + 어댑터 1M개만 훈련 → 1M개만 저장 (4MB)

비유:

파인튜닝 = 스마트폰 자체를 개조
  → 비싸고, 원본으로 못 돌아감

LoRA = 스마트폰에 케이스를 끼움
  → 싸고, 케이스만 바꾸면 됨, 폰은 그대로

작동 원리 — 행렬 분해

모델의 가중치는 큰 행렬이다. 예를 들어 768×768 = 589,824개 숫자.

LoRA는 이 큰 행렬을 직접 바꾸는 대신, 작은 행렬 2개를 옆에 붙인다:

원래: W (768×768) = 589,824개 숫자

LoRA: W(고정) + A(768×8) × B(8×768)
      589,824   + 6,144    + 6,144    = 어댑터는 12,288개만

A와 B를 곱하면 768×768 행렬이 나온다. 근데 중간 차원이 8(rank)이라서 숫자가 훨씬 적다. 이 A, B만 훈련하고, 원래 W는 건드리지 않는다.

# 추론 시:
output = W @ input + (A @ B) @ input
         ↑ 고정      ↑ LoRA 어댑터

코드로 해보기

pip install peft transformers datasets

from transformers import AutoModelForSequenceClassification, AutoTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset

# 1. 베이스 모델
model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased", num_labels=2)
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")

# 2. LoRA 설정 — 어떤 레이어에 어댑터를 끼울지
lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    r=8,                    # rank (작을수록 가벼움, 보통 4~16)
    lora_alpha=16,          # 스케일링 팩터
    lora_dropout=0.1,
    target_modules=["q_lin", "v_lin"]  # attention의 Q, V 행렬에만 끼움
)

# 3. LoRA 적용 — 베이스 모델을 얼리고 어댑터를 붙임
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# → trainable params: 296,450 || all params: 67,251,714 || trainable%: 0.44%
# 66M 중 30만 개만 훈련! (0.44%)

# 4. 데이터 + 훈련 (파인튜닝과 동일)
dataset = load_dataset("imdb")
dataset = dataset.map(lambda x: tokenizer(x["text"], truncation=True, padding="max_length", max_length=512), batched=True)

trainer = Trainer(
    model=model,
    train_dataset=dataset["train"].select(range(1000)),
    args=TrainingArguments(output_dir="./lora-model", num_train_epochs=3, per_device_train_batch_size=8)
)
trainer.train()

# 5. 저장 — 어댑터만 저장됨 (수 MB)
model.save_pretrained("./lora-adapter")
# → ./lora-adapter/adapter_model.safetensors (수 MB)
# 베이스 모델 260MB는 따로 저장 안 해도 됨 (Hub에서 다시 받으면 됨)

파인튜닝 vs LoRA 비교

파인튜닝 LoRA
훈련 대상 66M개 전부 30만 개 (0.44%)
저장 크기 260 MB 수 MB
GPU 메모리 많이 필요 적게 필요
훈련 속도 보통 빠름
품질 기준 거의 동일
용도별 전환 모델 전체 교체 어댑터만 교체

핵심

파인튜닝이 "폰 자체를 개조"라면, LoRA는 "케이스만 끼우기"다. 용도별로 케이스(어댑터)만 바꾸면 된다. 감정 분석용 어댑터, 번역용 어댑터, 코드 생성용 어댑터 — 베이스 모델 하나에 어댑터만 교체.

핵심 개념

1

베이스 모델을 얼린다(freeze) — 66M개 숫자를 고정, 변경 불가로 설정

2

작은 행렬 A(768×8), B(8×768)를 각 레이어에 끼운다 — 어댑터

3

A와 B만 훈련한다 — 전체의 0.44%만 조정

4

추론 시: output = W×input + (A×B)×input — 원래 가중치 + 어댑터 보정

5

어댑터만 저장 (수 MB) — 용도별로 어댑터만 교체하면 됨

사용 사례

대형 모델 파인튜닝 — 7B/13B 모델을 GPU 1개로 훈련 가능 다용도 모델 — 베이스 모델 1개 + 어댑터 N개로 여러 태스크 지원 배포 크기 절감 — 260MB 대신 수 MB 어댑터만 배포