08. 하이퍼파라미터 튜닝 가이드#
📚 학습 목표: 학습 설정을 상황에 맞게 조정하기
⏱️ 읽는 시간: 20분
🎯 원칙: 처음에는 기본값, 결과 보고 조정
왜 하이퍼파라미터가 중요한가#
같은 데이터라도 하이퍼파라미터에 따라: - 학습 시간 2-5배 차이 - 최종 품질 10-30% 차이 - Overfitting 유무 결정
하지만: 처음부터 튜닝하려고 하지 마세요. 기본값이 90% 상황에서 최선.
🎯 우리 프로젝트 기본값 (추천)#
scripts/02b_train_mlx.sh 상단에 정의:
LORA_R=32 # LoRA 용량
LORA_ALPHA=64 # LoRA 스케일링
LEARNING_RATE=2e-4 # 학습률
BATCH_SIZE=2 # 배치 크기
ITERS=600 # 반복 횟수 (데이터량에 따라)
SAVE_EVERY=100 # 체크포인트 주기
이 값들이 왜 기본값인가: 수많은 LoRA 연구에서 한국어 중간 크기 모델(4B)에 적절하다고 검증된 값들.
1. LoRA Rank (r) - 가장 중요한 파라미터#
의미#
LoRA 어댑터의 "용량". 모델이 얼마나 많은 새 지식을 추가로 담을 수 있는지.
값별 특성#
| r 값 | 어댑터 크기 | 학습 시간 | 적합한 경우 |
|---|---|---|---|
| 8 | 약 50MB | 빠름 | 간단한 태스크, 포맷 학습 |
| 16 | 약 150MB | 중간 | 단일 도메인 특화 |
| 32 | 약 300MB | 표준 | 범용 도메인 모델 ⭐ |
| 64 | 약 600MB | 느림 | 많은 복잡한 지식 |
| 128 | 약 1.2GB | 매우 느림 | 연구용, 거의 사용 안 함 |
우리 상황: r=32 이유#
- 5개 카테고리 균형 학습 → 중간 용량 필요
- 맥미니 24GB → r=64도 가능하지만 학습 시간 1.5배
- 300MB 어댑터 → HuggingFace 업로드/관리 편리
조정 시나리오#
r 증가 (32 → 64): - 언제: 카테고리 늘어남, 데이터 5,000건+ - 비용: 학습 시간 +50% - 주의: 데이터 적으면 overfitting 심해짐
r 감소 (32 → 16): - 언제: 빠른 반복 실험, 단일 태스크 - 이득: 학습 시간 -30% - 주의: 복잡한 패턴 학습 약해짐
2. LoRA Alpha (α) - r의 짝꿍#
의미#
LoRA 업데이트의 "강도" 스케일링. actual_update = (α/r) × learned_update
표준 공식#
α = 2 × r (LoRA 논문 권장)
| r | α | α/r |
|---|---|---|
| 8 | 16 | 2.0 |
| 16 | 32 | 2.0 |
| 32 | 64 | 2.0 ⭐ |
| 64 | 128 | 2.0 |
조정이 필요한 경우#
99% 상황에서 기본값 사용. 조정하는 케이스:
- α/r = 1.0: 보수적 학습 (원본 많이 유지)
- α/r = 2.0: 표준 ⭐
- α/r = 4.0: 공격적 학습 (원본 많이 변경)
3. Learning Rate (lr) - 민감도 결정#
의미#
한 번에 파라미터를 얼마나 크게 바꿀지.
왜 민감한가#
- 너무 크면: Loss 폭주 (NaN)
- 너무 작으면: 학습 거의 안 됨
값별 특성#
| lr | 특성 | 적합 |
|---|---|---|
| 1e-3 | 공격적 | 짧은 학습 (100 iter 이하) |
| 5e-4 | 빠름 | 풍부한 데이터 |
| 2e-4 | 표준 | LoRA 일반 ⭐ |
| 1e-4 | 보수적 | 안정적 학습 |
| 5e-5 | 매우 보수적 | 섬세한 튜닝 |
LR 스케줄 (자동 변경)#
우리 설정은 cosine을 사용:
Iter 0: lr = 2e-4
Iter 100: lr = 1.95e-4 ← 천천히 감소
Iter 300: lr = 1e-4
Iter 500: lr = 2.5e-5
Iter 600: lr = 0 ← 마지막엔 0 근처
왜: 초반엔 많이 배우고, 후반엔 미세 조정.
조정 시나리오#
lr 증가 (2e-4 → 5e-4): - Loss가 거의 감소 안 할 때 - 데이터 매우 풍부 (5,000건+)
lr 감소 (2e-4 → 1e-4): - Loss가 폭주하거나 진동할 때 - 섬세한 도메인 (의료 용어 등)
4. Batch Size - 메모리 trade-off#
의미#
한 번의 업데이트에 몇 개 샘플을 같이 볼지.
맥미니 24GB 기준#
| batch_size | 메모리 사용 | It/sec | 품질 |
|---|---|---|---|
| 1 | 8GB | 1.2 | 불안정 |
| 2 | 10GB | 0.9 | 표준 ⭐ |
| 4 | 14GB | 0.5 | 안정적 |
| 8 | 22GB | 0.25 | 빡빡 (OOM 위험) |
조정 시나리오#
batch_size 증가 (2 → 4): - 메모리 여유 있을 때 - 더 안정적인 학습 원할 때 - 학습 시간 약 60% 증가
batch_size 감소 (2 → 1): - OOM 에러 날 때 - 더 빠른 실험 원할 때 - Loss 더 진동하지만 더 빠름
Gradient Accumulation (대안)#
메모리는 batch_size=2 유지, 효과는 더 큰 batch처럼:
5. Iterations (iters) - 학습 양#
의미#
총 몇 번 반복할지. 1 iter = 1번의 배치 업데이트.
계산법#
1 epoch = 데이터 총량 / batch_size
목표 epoch = 3 (LoRA 일반적)
예시:
- 500건 데이터, batch=2 → 1 epoch = 250 iter
- 3 epochs = 750 iter (⭐ 추천)
- 1,500건 데이터, batch=2 → 1 epoch = 750 iter
- 3 epochs = 2,250 iter
- 5,000건 데이터, batch=2 → 1 epoch = 2,500 iter
- 2 epochs = 5,000 iter (큰 데이터는 2 epoch도 충분)
데이터량별 권장 iter#
| 데이터량 | 권장 iter | 학습 시간 (M4 24GB) |
|---|---|---|
| 250건 (더미) | 600 | 1-2시간 |
| 500건 | 750 | 1.5-2.5시간 |
| 1,500건 (MVP) | 1,500-2,000 | 3-5시간 |
| 5,000건 | 3,000-5,000 | 8-12시간 |
| 10,000건 | 5,000-10,000 | 15-30시간 (Colab 추천) |
과유불급#
너무 적은 iter: - Underfitting (학습 부족) - Loss가 아직 높은데 끝남
너무 많은 iter: - Overfitting (외우기) - Val loss가 다시 올라감
이상적: Val loss가 가장 낮은 지점에서 멈춤 (자동 멈춤 기능 활용)
6. 기타 파라미터들#
lora_layers#
어느 층에 LoRA를 적용할지.
--num-layers 16 # 기본값: 마지막 16개 층
--num-layers 8 # 더 적게 (학습 빠름, 성능 약간 낮음)
--num-layers 32 # 더 많이 (학습 느림, 성능 약간 높음)
우리 추천: 16 (기본값)
save_every#
체크포인트 저장 주기.
- 너무 자주 (50): 디스크 낭비
- 너무 드물게 (500): 중단 시 복구 어려움
- 100: 균형 ⭐
val_batches#
검증 시 몇 개 배치 쓸지.
- 적으면: 검증 불안정
- 많으면: 학습 시간 증가
- 25: 균형
steps_per_report#
얼마나 자주 로그 출력할지.
steps_per_eval#
얼마나 자주 검증할지.
🎯 상황별 프리셋 모음#
복사해서 바로 쓸 수 있는 설정들.
프리셋 1: 첫 실험 (더미 데이터) ⭐ 추천 시작점#
용도: 파이프라인 검증, 더미 250건
프리셋 2: MVP (500-1500건 실제 데이터) ⭐ 주력#
용도: 메디콘솔 v1.0.0 정식 학습
프리셋 3: 빠른 실험 (소규모)#
용도: 하이퍼파라미터 A/B 테스트, 1시간 이내
프리셋 4: 고품질 (풍부한 데이터)#
용도: 5,000건+ 데이터, 맥미니 24GB 풀가동
프리셋 5: 메모리 절약 (급할 때)#
용도: 다른 앱과 병행, 메모리 압박 시
🔬 A/B 테스트 방법#
두 설정 중 어떤 게 나은지 비교하려면:
1. 첫 번째 실험#
2. 두 번째 실험#
# 설정 변경 (예: r=32 → r=16)
# scripts/02b_train_mlx.sh 수정
# 학습
bash scripts/02b_train_mlx.sh
# 결과 보관
mv outputs/mlx_adapter outputs/mlx_adapter_B
3. 평가 비교#
# A 버전
cp outputs/mlx_adapter_A/adapters.safetensors outputs/mlx_adapter/
make fuse-mlx convert deploy evaluate
# 결과 저장: eval_report_A.json
# B 버전
cp outputs/mlx_adapter_B/adapters.safetensors outputs/mlx_adapter/
make fuse-mlx convert deploy evaluate
# 결과 저장: eval_report_B.json
# 비교
diff eval_report_A.json eval_report_B.json
실용 팁#
A/B 테스트는 최소 1주일 일정 확보 후 시작. 성급한 비교는 노이즈에 현혹되기 쉬움.
📊 Loss 값으로 판단하는 법#
학습 완료 후 Train/Val loss를 보고:
좋은 패턴#
→ 건강함. 그대로 사용.
Overfitting 패턴#
→ 외우기. iter 줄이거나 데이터 추가.
Underfitting 패턴#
→ 덜 학습됨. iter 늘리거나 LR 증가.
⚠️ 흔한 실수#
실수 1: "크게 하면 좋을 것"#
결과: OOM + 너무 긴 시간 + Overfitting
교훈: 기본값으로 시작, 부족할 때만 증가
실수 2: 한 번에 여러 파라미터 변경#
뭐 때문에 2%p 올랐는지 모름.
교훈: 한 번에 하나만 변경.
실수 3: 평가 없이 계속 튜닝#
"Loss가 더 낮아졌으니 좋아졌겠지" → 실제 답변 품질은 떨어질 수 있음.
교훈: make evaluate로 실제 품질 확인.
실수 4: 과도한 튜닝#
5% 개선하려고 2주 쓰기 → 데이터 추가 2주가 10% 개선
교훈: 데이터 > 하이퍼파라미터. 막히면 데이터로 돌아가기.
🎓 튜닝 결정 트리#
학습 결과 안 좋음?
│
├── Loss 감소 없음?
│ └── lr을 높여라 (2e-4 → 5e-4)
│
├── Loss NaN 폭주?
│ └── lr을 낮춰라 (2e-4 → 5e-5)
│
├── Overfitting (Train << Val)?
│ ├── iter 줄여라 (2000 → 1000)
│ ├── 데이터 추가
│ └── r 줄여라 (32 → 16)
│
├── Underfitting (둘 다 높음)?
│ ├── iter 늘려라 (1000 → 2000)
│ ├── r 늘려라 (32 → 64)
│ └── 데이터 품질 확인
│
├── 특정 카테고리만 약함?
│ └── 해당 카테고리 데이터 추가 (하이퍼파라미터 X)
│
└── 전체적으로 품질 낮음?
└── 데이터 품질 검토 (이게 답인 경우 많음)
✅ 자가 체크#
- 기본값이 왜 그 값인지 감 잡음
- 각 파라미터가 메모리/속도/품질에 어떻게 영향 주는지 알고 있음
- 상황별 프리셋 중 하나 선택 가능
- Loss 패턴 보고 어떤 파라미터 조정할지 판단 가능
- A/B 테스트 방법 알고 있음
다음 단계#
학습 설정 이해했다면, 결과 평가로 갑니다.
다음 문서: 09_EVALUATION.md - 품질 평가 방법
💡 핵심 요약#
처음: 기본값 (r=32, α=64, lr=2e-4, batch=2, iters=1500)
막히면: 한 번에 하나만 변경
원칙: 데이터 > 하이퍼파라미터
판단: Loss 그래프 + 실제 평가 점수