Unsloth - Lora Fine-Tuning Hyperparameters
1. LoRA 핵심 개념
1.1 문제: Full Fine-Tuning
Full Fine-Tuning은 모든 파라미터 \(W \in \mathbb{R}^{d \times k}\)를 업데이트한다.
\[W' = W_0 + \Delta W\]문제점: 70B 모델 기준 \(\Delta W\)만 해도 140GB+ 메모리 필요.
1.2 LoRA의 핵심
Fine-tuning시 weight 변화량 \(\Delta W\)는 Low-Rank 구조를 가진다.
Low-Rank = 압축 가능하다는 뜻
JPEG 압축처럼 원본 10MB → 500KB로 줄여도 품질이 비슷한 이유는,
정보에 중복과 패턴이 있기 때문.LLM Fine-tuning도 마찬가지.
연구 결과, weight 변화량이 엄청 복잡하게 변하는 게 아니라
몇 개의 주요 방향으로만 변한다는 것을 발견 이미 언어를 잘 아는 LLM에게 “의료 용어 좀 더 잘 알아듣게” 같은 미세 조정만 하면 되기 때문
즉, \(\Delta W\)를 두 개의 작은 행렬로 분해 가능:
\[\Delta W = BA\] \[\begin{align} B &\in \mathbb{R}^{d \times r} \\ A &\in \mathbb{R}^{r \times k} \\ r &\ll \min(d, k) \end{align}\]1.3 파라미터 비교
| Full Fine-Tuning | LoRA (r=8) | |
|---|---|---|
| 파라미터 수 | \(d \times k\) | \(d \times r + r \times k\) |
| 예시 (4096×4096) | 16.7M | 65K |
| 압축률 | 1x | ~256x |
2. 수학적 구조
2.1 Forward Pass
\[h = W_0 x + \frac{\alpha}{r} \cdot BAx\]
Input x
│
├─────────────────┐
▼ ▼
┌───────┐ ┌───────┐
│ W₀ │ │ A │ (r × k)
│frozen │ └───┬───┘
└───┬───┘ ▼
│ ┌───────┐
│ │ B │ (d × r)
│ └───┬───┘
│ │ × (α/r)
▼ ▼
└────────(+)──────┘
│
▼
Output h
Matrix 크기 예제
구체적인 숫자로 이해해보자. d=64, k=32, r=4로 설정한 경우:
| 행렬 | 크기 | 파라미터 수 | 설명 |
|---|---|---|---|
| \(W_0\) | 64 × 32 | 2,048 | 원본 weight (frozen) |
| \(A\) | 4 × 32 | 128 | LoRA down-projection |
| \(B\) | 64 × 4 | 256 | LoRA up-projection |
| \(\Delta W = BA\) | 64 × 32 | - | \(W_0\)와 같은 크기로 복원 |
LoRA 파라미터: \(A + B = 128 + 256 = 384\) (Full의 18.75%)
2.2 초기화
- A: Kaiming/Gaussian 초기화
- B: Zero 초기화 → 학습 시작 시 \(\Delta W = BA = 0\)
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
nn.init.zeros_(self.lora_B) # 핵심: 0으로 시작
2.3 Scaling Factor α
\[\text{scaling} = \frac{\alpha}{r}\]| r | α | α/r | 효과 |
|---|---|---|---|
| 8 | 8 | 1.0 | 기본 |
| 8 | 16 | 2.0 | LoRA 효과 2배 |
| 16 | 16 | 1.0 | rank↑, 스케일 유지 |
3. 적용 위치 (Target Modules)
Transformer에서 LoRA 적용 가능한 위치:
[Attention] [MLP (SwiGLU)]
├── q_proj ✓ ├── gate_proj ✓
├── k_proj ✓ ├── up_proj ✓
├── v_proj ✓ └── down_proj ✓
└── o_proj ✓
권장: 전부 적용 (Unsloth 기본값)
4. Hyperparameters 정리
| Parameter | 권장값 | 설명 |
|---|---|---|
r |
16~64 | rank. 높을수록 표현력↑, 메모리↑ |
lora_alpha |
r과 동일 | scaling = α/r |
lora_dropout |
0 | 필요시 0.05 |
target_modules |
all | Attention + MLP 모두 |
5. QLoRA
Base model을 4-bit 양자화 + LoRA:
- Base: 4-bit (frozen)
- LoRA adapters: 16-bit (trainable)
6. Unsloth Code
실제 Text-to-SQL 파인튜닝을 위한 코드 구현입니다.
앞서 다룬 LoRA Hyperparameters가 코드에 어떻게 적용되는지 확인합니다.
6.1 Model & LoRA Configuration
Scaling Factor($\frac{\alpha}{r}$)를 설정하는 단계입니다.
# Initialize Model with LoRA settings
model = GptOssModel(
model_config=ModelConfig(
model_name="unsloth/gpt-oss-20b",
max_seq_length=4096
),
lora_config=LoRAConfig(
r=16, # Rank (r): SQL 로직 학습을 위해 8보다 높은 16 설정
lora_alpha=32 # Alpha (α): Scaling factor = 32/16 = 2.0
)
)
- r=16: SQL 쿼리 생성과 같은 복잡한 논리 구조를 학습하기 위해 기본값(8)보다 Rank를 높여 표현력(Expressiveness)을 확보
- lora_alpha=32: $\Delta W$의 영향력을 2배로 설정하여($\text{scaling}=2.0$), 새로운 데이터셋(SQL)의 특징을 더 강하게 반영
6.2 Trainer Configuration (SFT)
Unsloth의 장점인 메모리 효율성을 극대화하기 위한 SFTTrainer 설정입니다.
trainer = SFTTrainer(
model=model.model,
processing_class=model.tokenizer,
train_dataset=train_dataset,
args=SFTConfig(
# Memory Optimization
per_device_train_batch_size=8,
gradient_accumulation_steps=32, # Effective Batch Size = 8 * 32 = 256
# Optimizer & Precision
optim="adamw_8bit", # Optimizer State 메모리 절약
fp16=False,
bf16=True, # Ampere(RTX 30/40/6000) 이상에서 필수
# Learning Rate Schedule
learning_rate=2e-4,
warmup_steps=5,
max_steps=30,
output_dir="outputs",
),
)
- gradient_accumulation_steps=32:
- 물리적 메모리 한계로 Batch Size를 작게(8) 가져가는 대신, 32번의 step 동안 gradient를 누적해 업데이트
- 결과적으로 대용량 배치(256)로 학습하는 것과 유사한 수렴 안정성을 확보
- per_device_train_batch_size=8 이게 실제 batch size
- 수식: \(\text{Effective Batch Size} = \text{Micro Batch Size} \times \text{Accumulation Steps} \times \text{Num GPUs}\)
- \[\text{Total Batch Size} = 8 \times 32 \times 1 = \mathbf{256}\]
- 메모리는 배치 8만큼만 쓰면서, 학습 효과는 배치 256인 것처럼 낼 수 있음
- optim=”adamw_8bit”:
- 일반 AdamW(32-bit) 대비 Optimizer state가 차지하는 VRAM을 1/4 수준으로 줄여 OOM(Out of Memory)을 방지
- bf16=True:
- FP16보다 표현 가능한 수의 범위(Dynamic Range)가 넓어 학습 중 발산(NaN)할 확률이 낮습니다.
- RTX 6000 Pro 환경에 최적화
6.3 Training & Saving
전체 파라미터($W$)가 아닌 LoRA Adapter($A, B$)만 학습을 진행합니다.
# Start Training (Updates only A and B matrices)
trainer.train()
# Save LoRA Adapters
model.save("./text2sql_lora_model")
- 학습이 끝나면 원본 모델(GB 단위)은 그대로 두고, 학습된 LoRA weight(MB 단위)만 저장합니다.
- 추론 시에는 원본 모델에 이 Adapter를 동적으로 로드하여 사용하게 됩니다.