콘텐츠로 이동
Data Prep
상세

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 직접 조작

참고 자료

핵심 논문

  1. Hu et al. (2022). LoRA: Low-Rank Adaptation of Large Language Models. ICLR 2022.
  2. Dettmers et al. (2023). QLoRA: Efficient Finetuning of Quantized LLMs. NeurIPS 2023.
  3. Liu et al. (2024). DoRA: Weight-Decomposed Low-Rank Adaptation. ICML 2024.
  4. Kopiczko et al. (2024). VeRA: Vector-based Random Matrix Adaptation. ICLR 2024.
  5. 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

관련 개념