“LoRA 파인튜닝 (Low-Rank Adaptation)”은 현대 AI, 특히 대형 언어 모델(LLM)이나 이미지 생성 모델을 가장 효율적으로 미세조정(fine-tuning) 하는 방법 중 하나예요.
아래에서는 이론 → 직관적 비유 → 수학적 원리 → 실제 코드 → 실무 적용 → 고급 응용 → 주의점까지, 제가 드릴 수 있는 최선의 설명을 단계별로 정리하겠습니다.
길지만, 끝까지 읽으면 LoRA의 전체 원리가 완전히 이해될 거예요.
AI 모델은 보통 “사전학습(Pretraining)”과 “미세조정(Fine-tuning)”의 두 단계로 만들어집니다.
기존에는 “전체 모델”을 다시 학습시켰어요.
하지만 GPT-3, LLaMA 같은 대형 모델은 수십억~수천억 개 파라미터가 있어서, GPU 한 대로는 불가능합니다.
그래서 나온 개념이 Parameter Efficient Fine-Tuning (PEFT)입니다.
LoRA는 PEFT의 대표적인 방법이에요.
전통적인 파인튜닝은:
모델 전체를 다시 학습한다 → GPU 메모리 터짐, 시간 오래 걸림, 오버피팅 위험.
그래서 LoRA는 이렇게 합니다 👇
“거대한 모델의 뇌(파라미터)는 그대로 두고,
그 옆에 작은 보조 회로(저랭크 행렬)만 붙여서 학습하자.”
이 덕분에
✅ GPU 메모리는 90% 이상 절약되고,
✅ 성능은 거의 동일하게 유지됩니다.
Transformer나 LLM 내부에는 수많은 선형 변환이 있어요.
예를 들어, 어떤 층(layer)의 연산이 이렇게 생겼다고 합시다:
[
h = W x
]
여기서
LoRA는 이 W를 직접 수정하지 않습니다. 대신에,
( W' = W + \Delta W )
로 두고,
[
\Delta W = B A
]
라는 “작은 두 행렬의 곱” 형태로 제한합니다.
이 두 행렬은 크기가 매우 작아요.
아래는 PyTorch 기반의 최소 구현 예제입니다.
import torch
import torch.nn as nn
class LoRALayer(nn.Module):
def __init__(self, in_features, out_features, r=4):
super().__init__()
self.W = nn.Parameter(torch.randn(out_features, in_features))
self.W.requires_grad = False # 원래 모델은 freeze
# LoRA 추가 부분
self.A = nn.Parameter(torch.randn(r, in_features) * 0.01)
self.B = nn.Parameter(torch.randn(out_features, r) * 0.01)
def forward(self, x):
# 원래 가중치 + LoRA 업데이트
return x @ (self.W.T + (self.B @ self.A).T)
# 예시 실행
x = torch.randn(2, 10)
layer = LoRALayer(10, 5, r=2)
y = layer(x)
print(y.shape) # torch.Size([2, 5])
이 코드에서 핵심은:
self.W는 고정 (학습 안 함)self.A, self.B만 학습| 항목 | 기존 선형층 | LoRA 사용 시 |
|---|---|---|
| 입력/출력 크기 | 4096×4096 | 4096×4096 |
| 학습 파라미터 수 | 16,777,216 | 4096×r + r×4096 = 8192×r |
| r = 8일 때 | 약 1,600만 | 65,536개 |
👉 약 250배 이상 절감!
LoRA를 인간의 학습에 비유하면 이래요:
이미 “언어”를 완벽히 배운 사람이 새로운 분야(의학 논문, 법률 문서 등)를 배울 때,
다시 언어 전체를 배우는 게 아니라 “그 분야의 용어노트만 새로 추가하는 것”.
즉,
LoRA는 직접 구현할 수도 있지만,
실무에서는 PEFT (Parameter Efficient Fine-Tuning) 라이브러리를 사용합니다.
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
# 기존 LLM 불러오기 (예: LLaMA2)
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
# LoRA 설정
lora_config = LoraConfig(
r=8, # low rank
lora_alpha=32,
target_modules=["q_proj", "v_proj"], # Attention 부분만 LoRA 적용
lora_dropout=0.05
)
# LoRA 모델로 변환
lora_model = get_peft_model(model, lora_config)
lora_model.print_trainable_parameters()
출력:
trainable params: 4,200,000 || all params: 6,700,000,000 || trainable%: 0.06%
즉,
6.7B 파라미터 중 단 0.06%만 학습하면 파인튜닝이 가능!
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./lora-llama2-finetuned",
learning_rate=1e-4,
per_device_train_batch_size=2,
num_train_epochs=3,
)
trainer = Trainer(
model=lora_model,
args=training_args,
train_dataset=my_dataset,
)
trainer.train()
결과적으로 LoRA는:
LoRA의 진짜 매력은 모듈형 전이(Composable Adaptation) 에 있어요.
LoRA_A : 금융 데이터로 학습LoRA_B : 의료 데이터로 학습LoRA_C : 문학 스타일로 학습이 3개를 서로 다른 모듈로 저장한 다음,
필요할 때마다 교체하거나 합성할 수 있습니다.
model.load_adapter("LoRA_A")
model.load_adapter("LoRA_B")
model.merge_and_unload() # 병합해서 사용
이건 인간의 “다중 전문성”과 비슷해요.
즉, 하나의 두뇌(모델)에 여러 LoRA 기억을 꽂았다 뺐다 하는 구조죠.
| 문제 | 설명 |
|---|---|
| Representation Bottleneck | 너무 작은 rank(r)를 쓰면 학습된 정보가 부족할 수 있음 |
| Layer 선택의 중요성 | 모든 층에 LoRA를 다 붙이면 오히려 메모리 낭비 |
| 추론 시 추가 연산량 | ΔW = BA가 추가되므로 약간의 latency 증가 가능 |
| LoRA 병합 시 주의 | 여러 LoRA 모듈을 병합할 때 scale mismatch 문제 발생 가능 |
따라서,
| 기업/모델 | 적용 예시 |
|---|---|
| Stability AI | Stable Diffusion의 스타일 LoRA (화풍 별로 미세조정) |
| Meta | LLaMA2 모델의 도메인별 LoRA (법률, 금융, 코드 전용) |
| Microsoft | Azure OpenAI API 내 커스텀 LoRA fine-tune 옵션 제공 |
“기존 모델의 핵심을 그대로 유지하면서, 고객별 LoRA 모듈만 로드”
→ 학습 효율, 보안, 유지보수 측면 모두 이점이 큽니다.
| 항목 | 전통적 파인튜닝 | LoRA |
|---|---|---|
| 학습 파라미터 수 | 전체(100%) | <1% |
| GPU 요구량 | 높음 | 매우 낮음 |
| 학습 시간 | 길다 | 짧다 |
| 적용 유연성 | 낮음 | 모듈 교체 가능 |
| 모델 재사용성 | 낮음 | 높음 |
💡 즉:
LoRA는 “모델을 바꾸지 않고 모델을 바꿀 수 있게 해주는 기술”이에요.
LoRA는 단순한 효율화 기법이 아니라,
“거대한 AI 모델을 개인과 조직이 현실적으로 다룰 수 있게 만든 기술적 전환점”입니다.
이것이 LoRA가 오늘날 “파인튜닝의 표준”이 된 이유입니다.