LLM 25일 코스 - Day 20: PEFT 라이브러리 실전

Day 20: PEFT 라이브러리 실전

PEFT(Parameter-Efficient Fine-Tuning) 라이브러리는 LoRA, QLoRA 등을 간편하게 적용할 수 있게 해주는 Hugging Face 공식 라이브러리입니다. 오늘은 실제 모델에 LoRA를 적용하는 전체 과정을 다룹니다.

LoraConfig 설정하기

# pip install peft transformers bitsandbytes

from peft import LoraConfig, TaskType, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

# QLoRA를 위한 4bit 양자화 설정
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# 기본 모델 로드
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# LoRA 설정
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,    # 태스크 유형
    r=8,                              # rank (저랭크 차원)
    lora_alpha=16,                    # 스케일링 팩터
    lora_dropout=0.05,                # 드롭아웃 (과적합 방지)
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],  # 적용할 레이어
    bias="none",                      # 바이어스 학습 여부
)

PEFT 모델 생성과 파라미터 확인

# LoRA 적용
peft_model = get_peft_model(model, lora_config)

# 학습 가능 파라미터 확인
peft_model.print_trainable_parameters()
# 출력 예: trainable params: 6,553,600 || all params: 8,036,098,048 || trainable%: 0.0816

# 어떤 레이어에 LoRA가 적용되었는지 확인
for name, param in peft_model.named_parameters():
    if param.requires_grad:
        print(f"학습 대상: {name} | shape: {param.shape}")

# 모델 구조에서 LoRA 레이어 확인
print(peft_model)

타겟 모듈 선택 가이드

어떤 레이어에 LoRA를 적용할지가 성능에 영향을 미칩니다. 모델의 Attention 모듈이 기본 대상이며, MLP 레이어까지 포함하면 성능이 더 올라갈 수 있습니다.

# 모델의 모든 Linear 레이어 이름 확인
def find_linear_modules(model):
    """LoRA 적용 가능한 Linear 레이어 목록을 반환"""
    linear_modules = set()
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear):
            # 마지막 부분만 추출 (예: model.layers.0.self_attn.q_proj -> q_proj)
            layer_name = name.split(".")[-1]
            linear_modules.add(layer_name)
    return list(linear_modules)

available_modules = find_linear_modules(model)
print(f"사용 가능한 타겟 모듈: {available_modules}")

# 일반적인 선택 가이드
target_guide = {
    "최소 (빠른 학습)":  ["q_proj", "v_proj"],
    "권장 (균형)":      ["q_proj", "v_proj", "k_proj", "o_proj"],
    "최대 (높은 성능)":  ["q_proj", "v_proj", "k_proj", "o_proj",
                         "gate_proj", "up_proj", "down_proj"],
}
for level, modules in target_guide.items():
    print(f"\n[{level}]: {modules}")

모델 저장과 로드

LoRA 어댑터는 원래 모델과 별도로 저장됩니다. 어댑터 크기는 수십 MB에 불과하여 공유와 버전 관리가 쉽습니다.

from peft import PeftModel

# LoRA 어댑터만 저장 (수십 MB)
peft_model.save_pretrained("./my_lora_adapter")

# 저장된 파일 확인
# ./my_lora_adapter/
#   adapter_config.json     (LoRA 설정)
#   adapter_model.safetensors  (학습된 가중치)

# 나중에 다시 로드
base_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
)
loaded_model = PeftModel.from_pretrained(base_model, "./my_lora_adapter")

# 추론 모드로 전환
loaded_model.eval()

# 어댑터를 기본 모델에 병합 (선택적 - 추론 속도 향상)
merged_model = loaded_model.merge_and_unload()
merged_model.save_pretrained("./merged_model")

오늘의 연습문제

  1. 작은 모델(예: gpt2 또는 distilgpt2)에 LoRA를 적용하고, print_trainable_parameters()로 학습 가능 파라미터 비율을 확인해보세요. target_modules를 바꿔가며 비교합니다.
  2. find_linear_modules() 함수를 사용하여 서로 다른 아키텍처의 모델(GPT-2, BERT, T5) 3개의 Linear 레이어 구조를 비교해보세요.
  3. LoRA 어댑터를 저장한 뒤 다시 로드하여, 저장 전과 동일한 출력을 생성하는지 검증해보세요. 같은 입력에 대한 logits를 비교합니다.

이 글이 도움이 되었나요?