Parameter-Efficient Fine-Tuning (PEFT)¶
개요¶
Parameter-Efficient Fine-Tuning(PEFT)은 대규모 사전학습 모델을 전체 파라미터 업데이트 없이 효율적으로 적응시키는 기법들의 총칭이다. 대표적인 방법인 LoRA(Low-Rank Adaptation)는 Microsoft에서 제안했으며, 모델 가중치의 업데이트를 저차원 행렬 분해로 근사하여 학습 가능한 파라미터 수를 99% 이상 줄인다.
| 항목 | 내용 |
|---|---|
| 논문 | LoRA: Low-Rank Adaptation of Large Language Models |
| 저자 | Edward J. Hu, Yelong Shen, Phillip Wallis, et al. |
| 발표 | ICLR 2022 |
| 분야 | Transfer Learning, Model Adaptation, Efficient Training |
핵심 아이디어¶
Full Fine-Tuning의 한계¶
기존 Fine-Tuning:
Pre-trained Model (175B params) -> Full Update -> Task-specific Model
문제점:
1. 메모리: GPT-3 175B 전체 gradient 저장 -> ~700GB VRAM
2. 저장: 태스크별 전체 모델 복사본 필요
3. 시간: 수십억 파라미터 업데이트
4. 비용: 대규모 GPU 클러스터 필수
Low-Rank Adaptation 직관¶
핵심 가정: Fine-tuning 과정의 가중치 변화는 저차원(low intrinsic rank)으로 표현 가능하다.
Pre-trained weights: W0 (d x k)
Full fine-tuning: W = W0 + ΔW
LoRA의 핵심:
ΔW를 저차원으로 분해
ΔW = B × A
여기서:
A: (r x k), r << min(d, k)
B: (d x r)
학습 파라미터: r(d + k) << d*k
시각적 표현:
Original:
┌─────────────────┐
Input (k) ─────> │ W0 (frozen) │ ─────> Output (d)
└─────────────────┘
LoRA:
┌─────────────────┐
Input (k) ─────> │ W0 (frozen) │ ──┐
└─────────────────┘ │
+──> Output (d)
┌────┐ ┌────┐ │
Input (k) ─────> │ A │ ──> │ B │ ────┘
│r×k │ │d×r │
└────┘ └────┘
(trainable)
이론적 배경¶
Intrinsic Dimension¶
Aghajanyan et al. (2020)의 연구에 따르면, 사전학습된 언어 모델의 fine-tuning은 본질적으로 저차원 공간에서 이루어진다.
실험 결과 (RoBERTa-large):
- 전체 파라미터: 355M
- Intrinsic dimension: ~200 (0.00006%)
해석:
- Fine-tuning의 자유도가 생각보다 훨씬 낮음
- 고차원 파라미터 공간 대부분이 redundant
- 저차원 근사로 충분히 표현 가능
LoRA 수학적 정의¶
Transformer의 attention layer에서 query, key, value, output projection에 적용:
Forward pass:
h = W0·x + ΔW·x
= W0·x + B·A·x
= W0·x + (α/r)·B·A·x (scaling factor 적용)
초기화:
A: Gaussian initialization
B: Zero initialization (시작 시 ΔW = 0)
학습:
W0: frozen (gradient 계산 안함)
A, B: trainable
Scaling factor의 역할:
α (alpha): rank-independent scaling
h = W0·x + (α/r)·B·A·x
이유:
- r이 변해도 동일한 learning rate 사용 가능
- α=r로 설정하면 scaling=1 (기본값)
- α>r: LoRA 기여도 증가
- α<r: LoRA 기여도 감소
왜 효과적인가?¶
1. Parameter Efficiency
예시: LLaMA-7B, rank=8
- 전체 파라미터: 6.7B
- LoRA 파라미터: ~4M (0.06%)
- 메모리 절감: ~95%
2. Training Stability
- W0 frozen -> pre-trained 지식 보존
- 작은 파라미터 -> overfitting 방지
- Zero initialization -> 점진적 적응
3. Modularity
- 태스크별 A, B 저장 (수 MB)
- 동적 로딩/스왑 가능
- 여러 LoRA 동시 활성화
PEFT 방법론 분류¶
1. Additive Methods (LoRA 계열)¶
모델에 추가 파라미터를 삽입한다.
LoRA Family:
┌─────────────────────────────────────────────────────┐
│ LoRA (2021) │
│ W = W0 + B·A │
└────────────────────────┬────────────────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
v v v
┌─────────┐ ┌─────────┐ ┌─────────┐
│ QLoRA │ │ DoRA │ │ VeRA │
│ (2023) │ │ (2024) │ │ (2024) │
│4-bit + │ │Weight │ │Shared │
│LoRA │ │Decomp. │ │vectors │
└─────────┘ └─────────┘ └─────────┘
2. Adapter-Based Methods¶
기존 레이어 사이에 작은 모듈을 삽입한다.
Transformer Block with Adapters:
Input
│
v
┌──────────────┐
│ Self-Attn │
└──────┬───────┘
│
┌────v────┐
│ Adapter │ <-- Trainable (down + up projection)
└────┬────┘
│ + (residual)
v
┌──────────────┐
│ Feed-Fwd │
└──────┬───────┘
│
┌────v────┐
│ Adapter │ <-- Trainable
└────┬────┘
│ + (residual)
v
Output
대표 방법들:
| 방법 | 논문 | 파라미터 | 특징 |
|---|---|---|---|
| Houlsby Adapter | ICML 2019 | 3-5% | 원조 Adapter |
| AdapterFusion | EACL 2021 | 가변 | Multi-task 조합 |
| Parallel Adapter | EMNLP 2021 | 2-3% | 병렬 삽입 |
| IA3 | NeurIPS 2022 | 0.01% | Element-wise scaling |
3. Prompt-Based Methods¶
입력에 학습 가능한 토큰을 추가한다.
Prefix-Tuning:
Original input: [CLS] This movie is great [SEP]
With prefix: [P1][P2]...[Pk] [CLS] This movie is great [SEP]
└────────┘
Trainable
Prompt Tuning (Soft Prompts):
- 임베딩 공간에서 직접 학습
- 실제 토큰과 무관한 연속 벡터
4. Selective Methods (Partial Fine-Tuning)¶
특정 레이어/파라미터만 업데이트한다.
BitFit: Bias만 학습
- 전체 중 ~0.1% 파라미터
- 놀라울 정도로 효과적
Layer-wise Training:
- Top layers만 학습
- Task-specific head 추가
LoRA 변형들¶
QLoRA (2023)¶
4-bit 양자화와 LoRA를 결합한다.
핵심 기술:
1. 4-bit NormalFloat (NF4): 정규분포에 최적화된 양자화
2. Double Quantization: 양자화 상수도 양자화
3. Paged Optimizers: GPU OOM 방지
메모리 비교 (LLaMA-65B):
- Full FT: ~780GB
- LoRA (16-bit): ~160GB
- QLoRA (4-bit): ~48GB (단일 GPU 가능!)
DoRA (2024)¶
Weight magnitude과 direction을 분리한다.
기존 LoRA:
W = W0 + B·A
DoRA:
W = m · (W0 + B·A) / ||W0 + B·A||
m: magnitude (scalar, trainable)
direction: normalized weight matrix
장점:
- Full fine-tuning과 유사한 학습 동역학
- 작은 rank에서도 높은 성능
VeRA (2024)¶
공유 랜덤 행렬로 파라미터를 더 줄인다.
LoRA: 각 레이어별 A, B 행렬
VeRA: 모든 레이어가 동일한 A, B 공유
레이어별 scaling vectors (d, b)만 학습
h = W0·x + Λb · B · Λd · A · x
파라미터 비교 (LLaMA-7B, r=256):
- LoRA: ~10M
- VeRA: ~1M (10x 감소)
LoRA+ (2024)¶
A와 B에 다른 learning rate 적용한다.
관찰:
- A와 B의 최적 learning rate가 다름
- B는 A보다 더 높은 lr이 효과적
LoRA+:
lr_B = λ · lr_A (λ ≈ 16 권장)
결과:
- 동일 학습 시간에 더 빠른 수렴
- 1-2% 성능 향상
GraLoRA (2026, 최신)¶
Granular rank allocation으로 레이어별 최적화한다.
문제: 모든 레이어에 동일한 rank는 비효율적
GraLoRA:
1. Gradient sensitivity 분석
2. 중요 레이어에 높은 rank 할당
3. 덜 중요한 레이어는 낮은 rank
결과:
- HumanEval+ Pass@1: +8.5% (vs LoRA)
- 동일 파라미터 예산에서 더 나은 성능
적용 위치¶
Attention Layers¶
Transformer Attention:
Q = W_q · x <-- LoRA 적용
K = W_k · x <-- LoRA 적용
V = W_v · x <-- LoRA 적용
O = W_o · Attn(Q,K,V) <-- LoRA 적용
일반적 권장:
- 최소: Q, V (query, value)
- 권장: Q, K, V, O (전체 attention)
Feed-Forward Layers¶
FFN:
h = W_up · σ(W_gate · x) (gated FFN)
out = W_down · h
적용 여부:
- 일부 연구: FFN에도 LoRA 적용 시 효과적
- Trade-off: 파라미터 증가 vs 성능 향상
- LLM에서는 attention만 적용하는 경우가 많음
Python 구현¶
기본 LoRA Layer¶
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class LoRALinear(nn.Module):
"""LoRA를 적용한 Linear Layer"""
def __init__(
self,
in_features: int,
out_features: int,
rank: int = 4,
alpha: float = 1.0,
dropout: float = 0.0,
merge_weights: bool = False
):
"""
Args:
in_features: 입력 차원
out_features: 출력 차원
rank: LoRA rank (r)
alpha: Scaling factor
dropout: LoRA dropout
merge_weights: 추론 시 가중치 병합 여부
"""
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.rank = rank
self.alpha = alpha
self.scaling = alpha / rank
self.merge_weights = merge_weights
self.merged = False
# Pre-trained weight (frozen)
self.weight = nn.Parameter(torch.empty(out_features, in_features))
self.bias = nn.Parameter(torch.zeros(out_features))
# LoRA matrices
self.lora_A = nn.Parameter(torch.empty(rank, in_features))
self.lora_B = nn.Parameter(torch.empty(out_features, rank))
# Dropout
self.lora_dropout = nn.Dropout(dropout) if dropout > 0 else nn.Identity()
self.reset_parameters()
def reset_parameters(self):
# Standard initialization for base weight
nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
# LoRA initialization
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
nn.init.zeros_(self.lora_B) # B를 0으로 초기화 -> 초기 ΔW = 0
def merge(self):
"""LoRA 가중치를 기본 가중치에 병합 (추론 최적화)"""
if not self.merged:
self.weight.data += self.scaling * (self.lora_B @ self.lora_A)
self.merged = True
def unmerge(self):
"""병합 해제"""
if self.merged:
self.weight.data -= self.scaling * (self.lora_B @ self.lora_A)
self.merged = False
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self.merged:
# 병합된 상태: 일반 linear
return F.linear(x, self.weight, self.bias)
else:
# 분리 상태: base + LoRA
base_out = F.linear(x, self.weight, self.bias)
lora_out = self.scaling * F.linear(
self.lora_dropout(x),
self.lora_B @ self.lora_A
)
return base_out + lora_out
@classmethod
def from_linear(
cls,
linear: nn.Linear,
rank: int = 4,
alpha: float = 1.0,
dropout: float = 0.0
) -> 'LoRALinear':
"""기존 Linear layer를 LoRALinear로 변환"""
lora_linear = cls(
in_features=linear.in_features,
out_features=linear.out_features,
rank=rank,
alpha=alpha,
dropout=dropout
)
# Pre-trained weight 복사
lora_linear.weight.data = linear.weight.data.clone()
if linear.bias is not None:
lora_linear.bias.data = linear.bias.data.clone()
# Pre-trained weight freeze
lora_linear.weight.requires_grad = False
lora_linear.bias.requires_grad = False
return lora_linear
class LoRAConfig:
"""LoRA 설정"""
def __init__(
self,
rank: int = 8,
alpha: float = 16,
dropout: float = 0.05,
target_modules: list = None,
fan_in_fan_out: bool = False
):
self.rank = rank
self.alpha = alpha
self.dropout = dropout
self.target_modules = target_modules or ['q_proj', 'v_proj']
self.fan_in_fan_out = fan_in_fan_out
모델에 LoRA 적용¶
from typing import Dict, Optional
import re
def apply_lora_to_model(
model: nn.Module,
config: LoRAConfig,
verbose: bool = True
) -> nn.Module:
"""
모델의 특정 레이어에 LoRA 적용
Args:
model: Base model
config: LoRA configuration
verbose: 변환 로그 출력
Returns:
LoRA가 적용된 모델
"""
lora_params = 0
frozen_params = 0
for name, module in model.named_modules():
# target_modules에 매칭되는 레이어 찾기
if any(target in name for target in config.target_modules):
if isinstance(module, nn.Linear):
# Parent module 찾기
parent_name = '.'.join(name.split('.')[:-1])
child_name = name.split('.')[-1]
parent = model.get_submodule(parent_name) if parent_name else model
# LoRALinear로 교체
lora_linear = LoRALinear.from_linear(
module,
rank=config.rank,
alpha=config.alpha,
dropout=config.dropout
)
setattr(parent, child_name, lora_linear)
lora_params += config.rank * (module.in_features + module.out_features)
frozen_params += module.in_features * module.out_features
if verbose:
print(f"Applied LoRA to: {name}")
# 나머지 파라미터 freeze
for name, param in model.named_parameters():
if 'lora_' not in name:
param.requires_grad = False
if verbose and param.numel() > 0:
frozen_params += param.numel()
if verbose:
total = lora_params + frozen_params
print(f"\nLoRA Parameters: {lora_params:,} ({lora_params/total*100:.4f}%)")
print(f"Frozen Parameters: {frozen_params:,}")
return model
def get_lora_state_dict(model: nn.Module) -> Dict[str, torch.Tensor]:
"""LoRA 파라미터만 추출"""
return {
name: param for name, param in model.state_dict().items()
if 'lora_' in name
}
def load_lora_weights(
model: nn.Module,
lora_state_dict: Dict[str, torch.Tensor]
):
"""LoRA 가중치 로드"""
model_state = model.state_dict()
model_state.update(lora_state_dict)
model.load_state_dict(model_state)
QLoRA 스타일 양자화¶
import bitsandbytes as bnb
class QLoRALinear(nn.Module):
"""4-bit 양자화 + LoRA"""
def __init__(
self,
in_features: int,
out_features: int,
rank: int = 4,
alpha: float = 1.0,
compute_dtype: torch.dtype = torch.float16
):
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.rank = rank
self.scaling = alpha / rank
self.compute_dtype = compute_dtype
# 4-bit quantized base weight (frozen)
self.weight = bnb.nn.Params4bit(
torch.empty(out_features, in_features),
requires_grad=False,
compress_statistics=True,
quant_type='nf4' # NormalFloat4
)
# LoRA in full precision
self.lora_A = nn.Parameter(
torch.empty(rank, in_features, dtype=compute_dtype)
)
self.lora_B = nn.Parameter(
torch.empty(out_features, rank, dtype=compute_dtype)
)
self.reset_lora_parameters()
def reset_lora_parameters(self):
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
nn.init.zeros_(self.lora_B)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Base: 4-bit matmul (dequantize on-the-fly)
base_out = bnb.matmul_4bit(
x, self.weight.t(),
bias=None,
quant_state=self.weight.quant_state
)
# LoRA: full precision
x_float = x.to(self.compute_dtype)
lora_out = self.scaling * (x_float @ self.lora_A.t() @ self.lora_B.t())
return base_out + lora_out.to(base_out.dtype)
PEFT 학습 루프¶
from torch.utils.data import DataLoader
from transformers import AutoModelForCausalLM, AutoTokenizer
from tqdm import tqdm
class LoRATrainer:
"""LoRA Fine-tuning Trainer"""
def __init__(
self,
model: nn.Module,
tokenizer,
learning_rate: float = 2e-4,
weight_decay: float = 0.01,
warmup_steps: int = 100,
device: str = 'cuda'
):
self.model = model.to(device)
self.tokenizer = tokenizer
self.device = device
# LoRA 파라미터만 학습
lora_params = [p for n, p in model.named_parameters() if 'lora_' in n]
self.optimizer = torch.optim.AdamW(
lora_params,
lr=learning_rate,
weight_decay=weight_decay
)
self.warmup_steps = warmup_steps
self.global_step = 0
def get_lr_scale(self) -> float:
"""Linear warmup"""
if self.global_step < self.warmup_steps:
return self.global_step / self.warmup_steps
return 1.0
def train_epoch(self, dataloader: DataLoader) -> float:
self.model.train()
total_loss = 0
for batch in tqdm(dataloader, desc="Training"):
input_ids = batch['input_ids'].to(self.device)
attention_mask = batch['attention_mask'].to(self.device)
labels = batch['labels'].to(self.device)
# Forward
outputs = self.model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
# Backward
self.optimizer.zero_grad()
loss.backward()
# Learning rate schedule
for param_group in self.optimizer.param_groups:
param_group['lr'] *= self.get_lr_scale()
self.optimizer.step()
total_loss += loss.item()
self.global_step += 1
return total_loss / len(dataloader)
def save_lora(self, path: str):
"""LoRA 가중치만 저장"""
lora_state = get_lora_state_dict(self.model)
torch.save(lora_state, path)
print(f"Saved LoRA weights to {path}")
print(f"Size: {sum(p.numel() * p.element_size() for p in lora_state.values()) / 1024**2:.2f} MB")
def lora_finetuning_example():
"""Complete LoRA fine-tuning example"""
# 1. Load base model
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. Apply LoRA
config = LoRAConfig(
rank=8,
alpha=16,
dropout=0.05,
target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj']
)
model = apply_lora_to_model(model, config)
# 3. Prepare data
# ... data preparation code ...
# 4. Train
trainer = LoRATrainer(model, tokenizer)
for epoch in range(3):
loss = trainer.train_epoch(train_loader)
print(f"Epoch {epoch+1}, Loss: {loss:.4f}")
# 5. Save
trainer.save_lora("lora_weights.pt")
# 6. Inference with merged weights
for module in model.modules():
if isinstance(module, LoRALinear):
module.merge()
# Now model runs at full speed
Multi-LoRA 관리¶
class LoRAManager:
"""여러 LoRA 어댑터 관리"""
def __init__(self, base_model: nn.Module):
self.base_model = base_model
self.adapters: Dict[str, Dict[str, torch.Tensor]] = {}
self.active_adapter: Optional[str] = None
def add_adapter(self, name: str, state_dict: Dict[str, torch.Tensor]):
"""새 어댑터 등록"""
self.adapters[name] = state_dict
def load_adapter(self, name: str):
"""어댑터 활성화"""
if name not in self.adapters:
raise ValueError(f"Adapter '{name}' not found")
# 이전 어댑터 해제
if self.active_adapter:
self._unload_current()
# 새 어댑터 로드
load_lora_weights(self.base_model, self.adapters[name])
self.active_adapter = name
def _unload_current(self):
"""현재 어댑터 언로드 (LoRA 가중치 0으로)"""
for name, param in self.base_model.named_parameters():
if 'lora_A' in name:
nn.init.kaiming_uniform_(param)
elif 'lora_B' in name:
nn.init.zeros_(param)
def combine_adapters(
self,
adapter_weights: Dict[str, float]
) -> Dict[str, torch.Tensor]:
"""여러 어댑터의 가중 평균"""
combined = {}
for param_name in self.adapters[list(adapter_weights.keys())[0]]:
weighted_sum = sum(
self.adapters[adapter][param_name] * weight
for adapter, weight in adapter_weights.items()
)
combined[param_name] = weighted_sum
return combined
# 사용 예시
manager = LoRAManager(base_model)
manager.add_adapter("coding", torch.load("lora_coding.pt"))
manager.add_adapter("writing", torch.load("lora_writing.pt"))
manager.add_adapter("math", torch.load("lora_math.pt"))
# 코딩 태스크
manager.load_adapter("coding")
output = model.generate(...)
# 어댑터 조합
combined = manager.combine_adapters({"coding": 0.6, "math": 0.4})
load_lora_weights(model, combined)
실무 가이드라인¶
하이퍼파라미터 권장값¶
| 파라미터 | LLM (7B) | LLM (70B) | Vision | 설명 |
|---|---|---|---|---|
| Rank (r) | 8-64 | 16-128 | 4-16 | 높을수록 표현력 증가 |
| Alpha | r~2r | r~2r | r | Scaling factor |
| Dropout | 0.05-0.1 | 0.05 | 0.1 | Overfitting 방지 |
| LR | 1e-4~3e-4 | 5e-5~1e-4 | 1e-4 | Base model보다 높게 |
| Target | Q,K,V,O | Q,K,V,O | Q,V | 적용 레이어 |
Rank 선택 가이드¶
데이터셋 크기와 태스크 복잡도에 따른 권장 rank:
데이터셋 태스크 복잡도 권장 Rank
< 1K samples 단순 4-8
1K-10K 중간 8-16
10K-100K 복잡 16-64
> 100K 매우 복잡 64-256
경험적 규칙:
- 의심스러우면 r=16에서 시작
- 성능 포화 시 rank 줄이기
- Underfitting 시 rank 늘리기
메모리 비교 (LLaMA-7B)¶
| 방법 | 학습 메모리 | 저장 크기 | 상대 속도 |
|---|---|---|---|
| Full FT | ~120GB | 26GB | 1.0x |
| LoRA r=8 | ~18GB | 8MB | 1.1x |
| LoRA r=64 | ~22GB | 64MB | 1.05x |
| QLoRA r=16 | ~12GB | 16MB | 0.9x |
주의사항¶
1. Rank가 너무 낮으면
- Underfitting
- 복잡한 패턴 학습 실패
- 해결: rank 증가 또는 더 많은 레이어에 적용
2. Rank가 너무 높으면
- Overfitting (특히 작은 데이터셋)
- 메모리 효율 감소
- 해결: dropout 추가, rank 감소
3. Target modules 선택
- Attention만 vs FFN 포함
- 일반적으로 attention만으로 충분
- 복잡한 태스크는 FFN 추가 고려
4. Learning rate
- Full FT보다 높은 LR 필요 (10x~100x)
- QLoRA는 상대적으로 낮은 LR
관련 연구 흐름¶
Parameter-Efficient Fine-Tuning 발전사:
Feature Extraction (2018-)
└── Pre-trained 고정, classifier만 학습
Adapter (Houlsby, 2019)
└── 레이어 사이에 작은 모듈 삽입
Prefix-Tuning (Li & Liang, 2021)
└── Soft prompt 학습
LoRA (Hu et al., 2021)
└── 저차원 행렬 분해로 가중치 근사
│
├── QLoRA (2023): 4-bit 양자화 결합
│
├── DoRA (2024): Magnitude-direction 분해
│
├── VeRA (2024): 공유 random matrices
│
├── LoRA+ (2024): Asymmetric learning rates
│
└── GraLoRA (2026): Granular rank allocation
IA3 (Liu et al., 2022)
└── 0.01% 파라미터로 학습 (element-wise scaling)
ReFT (Wu et al., 2024)
└── Representation 직접 조작
참고 자료¶
핵심 논문¶
- Hu et al. (2022). LoRA: Low-Rank Adaptation of Large Language Models. ICLR 2022.
- Dettmers et al. (2023). QLoRA: Efficient Finetuning of Quantized LLMs. NeurIPS 2023.
- Liu et al. (2024). DoRA: Weight-Decomposed Low-Rank Adaptation. ICML 2024.
- Kopiczko et al. (2024). VeRA: Vector-based Random Matrix Adaptation. ICLR 2024.
- Houlsby et al. (2019). Parameter-Efficient Transfer Learning for NLP. ICML 2019.
Survey¶
- Lialin et al. (2023). Scaling Down to Scale Up: A Guide to PEFT. NeurIPS 2023 Tutorial.
- Han et al. (2024). Parameter-Efficient Fine-Tuning for Large Models: A Survey. TACL.
구현 라이브러리¶
- PEFT (Hugging Face): https://github.com/huggingface/peft
- LLaMA-Factory: https://github.com/hiyouga/LLaMA-Factory
- Unsloth: https://github.com/unslothai/unsloth
관련 개념¶
- Knowledge Distillation: 모델 압축
- Model Merging: LoRA 어댑터 병합
- Mixture-of-Experts: 조건부 계산