LoRA(Low-Rank Adaptation)는 대규모 언어 모델(LLM)을 효율적으로 파인튜닝하기 위한 혁신적인 기술입니다. Microsoft Research에서 개발된 이 방법은 거대한 사전학습 모델의 모든 파라미터를 업데이트하는 대신, 작은 학습 가능한 행렬을 추가하여 모델을 특정 태스크에 적응시킵니다.
LoRA는 거대한 백과사전(사전학습 모델)에 포스트잇(LoRA 어댑터)을 붙이는 것과 같습니다.
수식적 표현
기존 파인튜닝: W = W₀ + ΔW
LoRA: W = W₀ + BA
여기서,
Rank(계수) 는 행렬의 선형 독립인 행/열의 최대 개수를 의미합니다.
예시: 1000×1000 행렬, rank=10
LoRA의 파라미터 압축 계산을 자세히 설명드리겠습니다.
원본 행렬 W
LoRA 분해 (rank=10)
압축률 계산
압축률 = 원본 파라미터 수 / LoRA 파라미터 수
= 1,000,000 / 20,000
= 50배
일반화된 공식
이 예시에서는 d=k=1000, r=10이므로:
즉, 원래 100만개의 파라미터를 저장해야 했던 것을 2만개의 파라미터만으로 근사할 수 있어서 50배 압축이 됩니다.
Transformer는 크게 Multi-Head Attention과 Feed-Forward Network 블록으로 구성됩니다. LoRA는 이 중 선형 변환(Linear Transformation)이 일어나는 모든 위치에 적용 가능합니다.
표준 Attention 메커니즘
Q = XW_Q (Query projection)
K = XW_K (Key projection)
V = XW_V (Value projection)
O = Attention(Q,K,V)W_O (Output projection)
LoRA 적용 후
Q = X(W_Q + B_Q A_Q) = XW_Q + X(B_Q A_Q)
K = X(W_K + B_K A_K) = XW_K + X(B_K A_K)
V = X(W_V + B_V A_V) = XW_V + X(B_V A_V)
O = Attention(Q,K,V)(W_O + B_O A_O)
Query (W_Q) LoRA
Key (W_K) LoRA
Value (W_V) LoRA
Output (W_O) LoRA
표준 FFN 구조
FFN(x) = W_2 * ReLU(W_1 * x + b_1) + b_2
LoRA 적용
W_1 → W_1 + B_1 A_1 (up-projection)
W_2 → W_2 + B_2 A_2 (down-projection)
FFN에서 LoRA의 효과
하위 레이어 (1-6층)
중간 레이어 (7-18층)
상위 레이어 (19-32층)
# 예: GPT 모델에서 선택적 LoRA 적용
lora_config = {
"target_modules": [
"transformer.h.*.attn.c_attn", # Attention QKV
"transformer.h.*.attn.c_proj", # Attention output
"transformer.h.*.mlp.c_fc", # FFN up-projection
"transformer.h.*.mlp.c_proj" # FFN down-projection
],
"layers_to_apply": [20, 21, 22, 23, 24] # 상위 5개 레이어만
}
입력 데이터 흐름:
입력 임베딩: X ∈ ℝ^(batch_size × seq_len × d_model)
Attention 계산 (예: d_model=768, r=16)
원본: Q = X @ W_Q [seq_len × 768] @ [768 × 768] = [seq_len × 768]
LoRA: Q_lora = X @ A_Q @ B_Q [seq_len × 768] @ [768 × 16] @ [16 × 768]
최종: Q_final = Q + Q_lora * (α/r)
메모리 사용량
# 12개 헤드를 가진 Attention의 경우
for head in range(12):
# 각 헤드별로 독립적인 LoRA 적용 가능
Q_head = X @ W_Q[head] + X @ B_Q[head] @ A_Q[head]
# 또는 모든 헤드에 공유 LoRA
Q_all_heads = X @ W_Q_combined + X @ B_Q_shared @ A_Q_shared
# 비효율적: 먼저 BA를 계산
lora_weight = B @ A # [d × r] @ [r × k] = [d × k]
output = input @ (W + lora_weight)
# 효율적: 순차적 계산
output = input @ W + (input @ A) @ B # r이 작을 때 더 효율적
# 원본 경로와 LoRA 경로 병렬 계산
with torch.cuda.stream(lora_stream):
lora_output = input @ A @ B * scaling
main_output = input @ W
torch.cuda.synchronize()
final_output = main_output + lora_output
학습 완료 후 가중치 병합:
# 추론 전 병합
W_merged = W_original + B @ A * (alpha / r)
# 이후 LoRA 없이 일반 추론
output = input @ W_merged # 추가 연산 없음
이를 통해 추론 시 추가적인 레이턴시 없이 LoRA의 이점을 모두 활용할 수 있습니다.
QLoRA는 LoRA의 메모리 효율성을 극한까지 끌어올린 방법입니다:
4-bit NormalFloat (NF4) 양자화
Double Quantization
Paged Optimizers
양자화 과정:
1. 원본 가중치: W ∈ FP16
2. 정규화: W_norm = W / max(|W|)
3. NF4 양자화: W_nf4 = Quantize_NF4(W_norm)
4. 저장: W_nf4 (4-bit) + scale (FP16)
역양자화 (추론/학습 시):
W_dequant = Dequantize_NF4(W_nf4) × scale
# 순전파
W_dequant = dequantize(W_quantized) # 4-bit → FP16
output = input @ W_dequant + input @ A @ B # LoRA는 FP16 유지
# 역전파
grad_A, grad_B = compute_gradients(loss) # LoRA만 그래디언트 계산
# 원본 가중치는 그래디언트 계산 안 함 (메모리 절약)
| 구성요소 | FP16 | INT8 | NF4 (QLoRA) |
|---|---|---|---|
| 모델 가중치 | 130GB | 65GB | 35GB |
| LoRA 파라미터 | 200MB | 200MB | 200MB |
| 활성화값 | 추정 30GB | 추정 30GB | 추정 15GB |
| 총계 | 160GB+ | 95GB+ | 50GB |
AdaLoRA는 모든 레이어에 동일한 rank를 적용하는 대신, 중요도에 따라 rank를 동적으로 할당합니다.
문제 인식:
중요도 점수 계산:
# LoRA 행렬의 SVD
P = B @ A # LoRA 가중치
U, S, V = torch.svd(P)
# 특이값 기반 중요도
importance = sum(S[:k]) / sum(S) # 상위 k개 특이값의 비율
def adaptive_rank_allocation(importance_scores, total_budget):
"""
importance_scores: 각 레이어의 중요도
total_budget: 전체 파라미터 예산
"""
# 1. 중요도에 비례하여 초기 rank 할당
initial_ranks = proportional_allocation(importance_scores, total_budget)
# 2. 반복적 조정
for iteration in range(num_iterations):
# 각 레이어의 성능 기여도 측정
layer_gradients = compute_layer_gradients()
# 높은 기여도 레이어에 더 많은 rank 할당
adjusted_ranks = realloc_based_on_gradients(
initial_ranks,
layer_gradients
)
# 최소/최대 rank 제약
adjusted_ranks = clip(adjusted_ranks, min_rank=1, max_rank=64)
return adjusted_ranks
Phase 1: Warm-up (모든 레이어 동일 rank)
# 초기 몇 epoch는 동일한 rank로 학습
for layer in model.layers:
layer.lora_rank = initial_rank
train(model, warm_up_epochs)
Phase 2: 적응적 조정
# 중요도 기반 rank 재할당
for epoch in range(remaining_epochs):
# 중요도 계산
importance = calculate_importance(model)
# Rank 재할당
new_ranks = adaptive_rank_allocation(importance, budget)
# LoRA 행렬 크기 조정
for layer, new_rank in zip(model.layers, new_ranks):
resize_lora_matrices(layer, new_rank)
# 학습 계속
train(model, 1)
| 방법 | 평균 Rank | 파라미터 수 | GLUE 점수 |
|---|---|---|---|
| LoRA (r=8) | 8 | 4.7M | 83.5 |
| LoRA (r=16) | 16 | 9.4M | 84.9 |
| AdaLoRA | 8 (평균) | 4.7M | 85.2 |
최신 연구에서는 두 방법을 결합하여 더욱 효율적인 파인튜닝을 시도하고 있습니다:
class AdaptiveQLoRA:
def __init__(self, model, quant_config, ada_config):
# 모델 양자화 (QLoRA)
self.quantized_model = quantize_model(model, quant_config)
# 적응적 LoRA 설정
self.ada_config = ada_config
self.layer_ranks = {}
def train_step(self, batch):
# 1. 현재 중요도 계산
importance = self.calculate_importance()
# 2. Rank 동적 조정
if self.should_adjust_ranks():
self.adjust_ranks(importance)
# 3. 4-bit 모델에서 LoRA 학습
loss = self.forward_with_qlora(batch)
self.backward_lora_only(loss)
# A와 B 행렬에 다른 학습률 적용
optimizer = torch.optim.AdamW([
{'params': lora_A_params, 'lr': 1e-4},
{'params': lora_B_params, 'lr': 2e-4} # B에 더 높은 lr
])
class MultiLoRA(nn.Module):
def __init__(self, num_experts=4):
self.experts = nn.ModuleList([
LoRALayer(d, k, r) for _ in range(num_experts)
])
self.router = nn.Linear(d, num_experts)
def forward(self, x):
# 라우팅 가중치 계산
routing_weights = F.softmax(self.router(x), dim=-1)
# 전문가 출력 가중 합
output = sum(
w * expert(x)
for w, expert in zip(routing_weights, self.experts)
)
return output
특정 구조를 가진 LoRA 변형:
이러한 발전된 LoRA 변형들은 각각의 장단점을 가지며, 사용 사례에 따라 적절히 선택하여 활용할 수 있습니다.
특이값 분해(SVD) 관점:
ΔW = UΣV^T ≈ U_r Σ_r V_r^T
Transformer 아키텍처에서:
| 파라미터 | 일반적 범위 | 선택 기준 |
|---|---|---|
| Rank (r) | 1-64 | 태스크 복잡도에 비례 |
| Alpha (α) | r-2r | 학습 안정성 조절 |
| Dropout | 0.05-0.1 | 과적합 방지 |
| Target Modules | ["q_proj", "v_proj"] | 모델 아키텍처 의존 |
PyTorch/Transformers 생태계:
웹 개발자를 위한 접근:
LLaMA-2 7B 모델 기준:
| 구성요소 | 전체 파인튜닝 | LoRA | QLoRA |
|---|---|---|---|
| 모델 가중치 | 14GB | 14GB (읽기전용) | 3.5GB (4-bit) |
| 그래디언트 | 14GB | 14MB | 14MB |
| 옵티마이저 | 56GB | 56MB | 56MB |
| 활성화값 | 20-50GB | 10-20GB | 5-10GB |
| 총계 | 104-134GB | 24-34GB | 9-14GB |
| 모델 크기 | 전체 파인튜닝 | LoRA | QLoRA |
|---|---|---|---|
| 7B | 2×A100 40GB | 1×A100 40GB | 1×RTX 4090 |
| 13B | 4×A100 40GB | 1×A100 80GB | 1×A100 40GB |
| 70B | 8×A100 80GB | 2×A100 40GB | 1×A100 40GB |
1. 고객 서비스
2. 의료 분야
3. 법률 서비스
4. 교육 플랫폼
현재 연구 동향:
LoRA는 대규모 언어 모델의 민주화를 실현하는 핵심 기술입니다:
LoRA와 그 변형들(QLoRA, AdaLoRA 등)은 이미 LLM 파인튜닝의 de facto 표준이 되었으며, 앞으로도 더 많은 혁신을 가능하게 할 것입니다. 이 기술을 통해 개인 연구자부터 대기업까지 모두가 최첨단 AI 기술의 혜택을 누릴 수 있게 되었습니다.