파인튜닝 학습 파이프라인의 전체 구조를 설계하고, 학습률, 배치 크기, 스케줄링 등 핵심 하이퍼파라미터를 최적화하는 전략을 다룹니다.
파인튜닝 학습 파이프라인은 단순히 모델을 학습시키는 것 이상의 체계적인 과정입니다. 데이터 로딩, 전처리, 모델 설정, 학습 루프, 평가, 체크포인트 관리, 실험 추적까지 모든 요소가 유기적으로 연결되어야 합니다.
학습 파이프라인 전체 흐름:
설정 로드 (Config)
|
데이터 로드 및 전처리
|
모델 로드 및 PEFT 설정
|
학습 루프
|-- 순전파 (Forward Pass)
|-- 손실 계산 (Loss)
|-- 역전파 (Backward Pass)
|-- 그래디언트 클리핑
|-- 옵티마이저 스텝
|-- 학습률 스케줄링
|-- 로깅 (Wandb/TensorBoard)
|-- 평가 (주기적)
|-- 체크포인트 저장 (주기적)
|
최종 모델 저장
|
평가 및 보고서 생성재현 가능한 실험을 위해 모든 설정을 코드가 아닌 설정 파일로 관리하는 것이 중요합니다.
# config.py
from dataclasses import dataclass, field
@dataclass
class ModelConfig:
model_name: str = "meta-llama/Llama-3.1-8B-Instruct"
use_4bit: bool = True
bnb_4bit_quant_type: str = "nf4"
use_flash_attention: bool = True
@dataclass
class LoraConfig:
r: int = 32
lora_alpha: int = 64
lora_dropout: float = 0.05
target_modules: list[str] = field(default_factory=lambda: [
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
])
@dataclass
class TrainingConfig:
output_dir: str = "./output"
num_train_epochs: int = 3
per_device_train_batch_size: int = 2
gradient_accumulation_steps: int = 8
learning_rate: float = 2e-4
lr_scheduler_type: str = "cosine"
warmup_ratio: float = 0.1
weight_decay: float = 0.01
max_seq_length: int = 2048
max_grad_norm: float = 0.3
save_steps: int = 200
eval_steps: int = 200
logging_steps: int = 10
bf16: bool = True
gradient_checkpointing: bool = True
optim: str = "paged_adamw_8bit"
seed: int = 42
@dataclass
class DataConfig:
train_file: str = "data/train.jsonl"
val_file: str = "data/val.jsonl"
packing: bool = True
@dataclass
class ExperimentConfig:
model: ModelConfig = field(default_factory=ModelConfig)
lora: LoraConfig = field(default_factory=LoraConfig)
training: TrainingConfig = field(default_factory=TrainingConfig)
data: DataConfig = field(default_factory=DataConfig)
experiment_name: str = "baseline"YAML 기반 설정 관리도 실무에서 널리 사용됩니다.
# configs/experiment_v1.yaml
model:
model_name: meta-llama/Llama-3.1-8B-Instruct
use_4bit: true
bnb_4bit_quant_type: nf4
lora:
r: 32
lora_alpha: 64
lora_dropout: 0.05
training:
num_train_epochs: 3
per_device_train_batch_size: 2
gradient_accumulation_steps: 8
learning_rate: 0.0002
lr_scheduler_type: cosine
warmup_ratio: 0.1
max_seq_length: 2048
experiment_name: v1-baseline학습률은 파인튜닝에서 가장 중요한 하이퍼파라미터입니다. 너무 높으면 사전 학습된 지식이 파괴(Catastrophic Forgetting)되고, 너무 낮으면 학습이 진행되지 않습니다.
파인튜닝 방법별 권장 학습률:
방법 권장 범위 일반적인 값
Full Fine-Tuning 1e-6 ~ 5e-5 2e-5
LoRA 1e-4 ~ 5e-4 2e-4
QLoRA 1e-4 ~ 5e-4 2e-4
DPO 1e-6 ~ 5e-6 5e-7LoRA/QLoRA의 학습률이 Full Fine-Tuning보다 10배 이상 높은 것은 의외로 느껴질 수 있습니다. 이는 LoRA 어댑터의 파라미터 수가 훨씬 적고, 스케일링 팩터(alpha/r)가 적용되기 때문입니다. 원본 모델의 가중치는 고정되어 있으므로 더 큰 학습률을 사용해도 안전합니다.
학습률을 학습 과정에서 동적으로 조절하는 스케줄러의 선택도 중요합니다.
주요 학습률 스케줄러:
Cosine:
특징: 코사인 함수를 따라 학습률이 점진적으로 감소
장점: 부드러운 감소, 학습 후반부 안정적
용도: 대부분의 파인튜닝 작업에 권장
Linear:
특징: 선형으로 학습률 감소
장점: 단순하고 예측 가능
용도: 짧은 학습에 적합
Cosine with Restarts:
특징: 주기적으로 학습률을 재시작
장점: 지역 최적점 탈출 가능
용도: 긴 학습이나 다중 태스크 학습
Constant with Warmup:
특징: 워밍업 후 일정한 학습률 유지
장점: 구현 간단
용도: 짧은 학습이나 탐색 단계from transformers import get_scheduler
import torch
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4)
total_steps = 1000
# Cosine 스케줄러 (워밍업 포함)
scheduler = get_scheduler(
name="cosine",
optimizer=optimizer,
num_warmup_steps=100, # 전체의 10%
num_training_steps=total_steps,
)학습 초기에 학습률을 0에서 목표 값까지 점진적으로 올리는 기법입니다. 초기 불안정한 그래디언트로 인한 발산을 방지합니다.
워밍업 비율 가이드:
데이터셋 크기 권장 워밍업 비율
1,000개 이하 5% ~ 10%
1,000 ~ 10,000 3% ~ 5%
10,000 이상 1% ~ 3%배치 크기(Batch Size)는 한 번의 그래디언트 업데이트에 사용되는 데이터 수입니다. GPU 메모리 제약 하에서 효과적인 배치 크기를 달성하기 위해 그래디언트 누적(Gradient Accumulation)을 활용합니다.
실효 배치 크기 계산:
실효 배치 크기 = per_device_batch_size
x gradient_accumulation_steps
x num_gpus
예시:
per_device_batch_size = 2
gradient_accumulation_steps = 8
num_gpus = 1
실효 배치 크기 = 2 x 8 x 1 = 16배치 크기의 영향:
작은 배치 (4~8):
- 그래디언트 노이즈가 높아 정규화 효과
- 지역 최적점 탈출에 유리
- 학습 불안정할 수 있음
- 더 많은 업데이트 스텝 필요
중간 배치 (16~32):
- 노이즈와 안정성의 균형
- 대부분의 파인튜닝에 적합
- 권장 시작점
큰 배치 (64 이상):
- 안정적인 그래디언트
- 학습률을 비례하여 높일 수 있음
- 일반화 성능이 떨어질 수 있음
- 메모리 요구량 증가학습 과정을 체계적으로 추적하고 모니터링하는 것은 반복적인 실험의 핵심입니다.
import wandb
# wandb 초기화
wandb.init(
project="fine-tuning-guide",
name="qlora-llama3-8b-v1",
config={
"model": "meta-llama/Llama-3.1-8B-Instruct",
"method": "QLoRA",
"lora_r": 32,
"lora_alpha": 64,
"learning_rate": 2e-4,
"batch_size": 16,
"epochs": 3,
"dataset_size": 5000,
}
)
# TrainingArguments에 wandb 연동
training_args = TrainingArguments(
report_to="wandb",
run_name="qlora-llama3-8b-v1",
logging_steps=10,
# ...
)학습 중 모니터링 지표:
손실 관련:
- train_loss: 학습 손실 (감소해야 함)
- eval_loss: 검증 손실 (과적합 감지)
- train_loss - eval_loss: 과적합 갭
학습 역학:
- learning_rate: 현재 학습률
- grad_norm: 그래디언트 노름 (폭발/소실 감지)
- epoch: 현재 에포크
시스템 자원:
- GPU 메모리 사용량
- GPU 활용률
- 학습 속도 (samples/sec)검증 손실이 더 이상 개선되지 않으면 학습을 중단하여 과적합을 방지합니다.
from transformers import EarlyStoppingCallback
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
callbacks=[
EarlyStoppingCallback(
early_stopping_patience=3,
early_stopping_threshold=0.01
)
],
)조기 종료 설정 가이드:
patience (인내심):
- 검증 손실이 개선되지 않는 연속 평가 횟수
- 너무 작으면 학습이 조기에 중단될 수 있음
- 권장: 3~5회
threshold (임계값):
- 개선으로 인정하는 최소 변화량
- 너무 높으면 미세한 개선을 놓침
- 권장: 0.001~0.01최적의 하이퍼파라미터 조합을 찾기 위한 체계적인 탐색 방법을 소개합니다.
하이퍼파라미터 탐색 순서:
1단계: 학습률 탐색 (가장 중요)
- 후보: [5e-5, 1e-4, 2e-4, 5e-4]
- 1 에포크만 학습하여 빠르게 비교
- train_loss 감소 속도와 eval_loss 확인
2단계: LoRA 랭크 결정
- 후보: [8, 16, 32, 64]
- 최적 학습률에서 비교
- 성능과 메모리의 균형점 선택
3단계: 배치 크기 조정
- 후보: [8, 16, 32]
- 학습률을 배치 크기에 비례하여 조정
4단계: 에포크 수 결정
- 후보: [1, 2, 3, 5]
- 검증 손실의 수렴 시점 확인import optuna
from transformers import TrainingArguments
from trl import SFTTrainer
def objective(trial):
"""Optuna 목적 함수"""
# 탐색 공간 정의
lr = trial.suggest_float("learning_rate", 5e-5, 5e-4, log=True)
lora_r = trial.suggest_categorical("lora_r", [8, 16, 32, 64])
lora_alpha = lora_r * 2 # alpha = 2 * r 규칙
batch_size = trial.suggest_categorical("batch_size", [8, 16, 32])
warmup_ratio = trial.suggest_float("warmup_ratio", 0.03, 0.15)
weight_decay = trial.suggest_float("weight_decay", 0.0, 0.1)
# LoRA 설정
from peft import LoraConfig as PeftLoraConfig
lora_config = PeftLoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules="all-linear",
lora_dropout=0.05,
task_type="CAUSAL_LM",
)
# 모델 로드 및 설정 (매 trial마다)
model = load_base_model()
model = get_peft_model(model, lora_config)
# 학습 실행
training_args = TrainingArguments(
output_dir="./optuna-trial-" + str(trial.number),
num_train_epochs=1, # 탐색 시에는 1 에포크
per_device_train_batch_size=batch_size // 8,
gradient_accumulation_steps=8,
learning_rate=lr,
warmup_ratio=warmup_ratio,
weight_decay=weight_decay,
eval_strategy="epoch",
logging_steps=50,
bf16=True,
)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
)
result = trainer.train()
eval_result = trainer.evaluate()
return eval_result["eval_loss"]
# Optuna 스터디 실행
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=20)
print("최적 하이퍼파라미터: " + str(study.best_params))
print("최적 검증 손실: " + str(study.best_value))다중 GPU 환경에서 학습 속도를 높이는 기본적인 분산 학습 설정을 소개합니다.
# accelerate_config.yaml
# accelerate config 명령으로 생성 가능# 단일 GPU 학습
python train.py
# 다중 GPU 학습 (DDP)
accelerate launch --multi_gpu --num_processes 4 train.py
# DeepSpeed ZeRO Stage 2
accelerate launch --use_deepspeed \
--deepspeed_config_file ds_config.json \
train.py분산 학습 전략:
DDP (DistributedDataParallel):
- 각 GPU에 모델 복사본을 두고 데이터 병렬 처리
- 간단하고 안정적
- 모델이 단일 GPU에 올라갈 때 적합
FSDP (Fully Sharded Data Parallel):
- 모델 파라미터, 그래디언트, 옵티마이저 상태를 GPU 간 분할
- 큰 모델에 적합
- 설정이 복잡
DeepSpeed ZeRO:
- Stage 1: 옵티마이저 상태 분할
- Stage 2: 옵티마이저 + 그래디언트 분할
- Stage 3: 전체 분할 (FSDP와 유사)
- Offloading: CPU/NVMe로 데이터 이전 가능학습 도중 모델 상태를 저장하여 학습 재개, 최적 모델 선택 등을 가능하게 합니다.
training_args = TrainingArguments(
save_strategy="steps",
save_steps=200,
save_total_limit=3, # 최근 3개만 유지
load_best_model_at_end=True, # 최적 모델 자동 로드
metric_for_best_model="eval_loss",
greater_is_better=False,
)체크포인트 전략:
save_strategy="steps" + save_steps=200:
- 200 스텝마다 저장
- 긴 학습에서 중간 상태 보존에 유용
save_strategy="epoch":
- 에포크 완료마다 저장
- 짧은 학습에 적합
save_total_limit=3:
- 디스크 공간 절약
- 최근 N개만 유지
load_best_model_at_end=True:
- 학습 종료 시 최적 체크포인트 자동 로드
- eval_loss 기준으로 최적 모델 선택# 체크포인트에서 학습 재개
trainer.train(resume_from_checkpoint="./output/checkpoint-400")지금까지의 모든 요소를 통합한 완성된 학습 스크립트입니다.
# train.py
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
EarlyStoppingCallback,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer
import wandb
def main():
# 1. wandb 초기화
wandb.init(project="fine-tuning-guide")
# 2. 모델 로드
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 = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
quantization_config=bnb_config,
device_map="auto",
attn_implementation="flash_attention_2",
)
model = prepare_model_for_kbit_training(
model, use_gradient_checkpointing=True
)
tokenizer = AutoTokenizer.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct"
)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 3. LoRA 설정
lora_config = LoraConfig(
r=32,
lora_alpha=64,
target_modules="all-linear",
lora_dropout=0.05,
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 4. 데이터 로드
dataset = load_dataset("json", data_files={
"train": "data/train.jsonl",
"validation": "data/val.jsonl",
})
# 5. 학습 설정
training_args = TrainingArguments(
output_dir="./output",
num_train_epochs=3,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=2e-4,
lr_scheduler_type="cosine",
warmup_ratio=0.1,
weight_decay=0.01,
max_grad_norm=0.3,
logging_steps=10,
save_strategy="steps",
save_steps=200,
eval_strategy="steps",
eval_steps=200,
save_total_limit=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
bf16=True,
gradient_checkpointing=True,
gradient_checkpointing_kwargs={"use_reentrant": False},
optim="paged_adamw_8bit",
report_to="wandb",
seed=42,
)
# 6. 트레이너 설정 및 학습
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
tokenizer=tokenizer,
max_seq_length=2048,
packing=True,
callbacks=[
EarlyStoppingCallback(early_stopping_patience=3)
],
)
trainer.train()
# 7. 최적 모델 저장
trainer.save_model("./best-adapter")
wandb.finish()
if __name__ == "__main__":
main()이번 장에서는 학습 파이프라인의 전체 구조와 하이퍼파라미터 최적화 전략을 다루었습니다.
다음 장에서는 파인튜닝된 모델의 성능을 객관적으로 평가하고 벤치마킹하는 방법을 다룹니다. 자동 평가 메트릭, 인간 평가, A/B 비교 등 다각적인 평가 전략을 체계적으로 안내합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
파인튜닝된 모델의 성능을 자동 메트릭, LLM 평가, 인간 평가를 통해 다각적으로 측정하고 벤치마킹하는 체계적인 방법을 다룹니다.
4비트 양자화와 LoRA를 결합한 QLoRA의 원리를 이해하고, 단일 소비자 GPU에서 대규모 모델을 파인튜닝하는 실전 방법을 다룹니다.
파인튜닝된 모델을 체계적으로 관리하기 위한 모델 레지스트리 구축, 버전 관리, 메타데이터 추적, 아티팩트 저장 전략을 다룹니다.