初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm Source: Original Platform
This commit is contained in:
589
source/eval/plan/3B_MASTER_PLAN.md
Normal file
589
source/eval/plan/3B_MASTER_PLAN.md
Normal file
@@ -0,0 +1,589 @@
|
||||
# 🚀 3B 한국어 LLM 마스터 플랜
|
||||
|
||||
**작성일**: 2026-02-27 04:27 KST
|
||||
**프로젝트**: `/PROJECT/0325120031_A/ghong/taketimes/llm-bang/`
|
||||
**결정**: 1B → 3B 전환 (1B 구조적 한계 확인)
|
||||
**총 예상 기간**: ~35시간
|
||||
|
||||
---
|
||||
|
||||
## 0. 현황 요약
|
||||
|
||||
### 1B에서 확인된 것
|
||||
|
||||
| 항목 | 결과 |
|
||||
|------|------|
|
||||
| 반복률 (raw, 올바른 포맷) | 30.7% |
|
||||
| 반복률 (rep_penalty=1.1) | 18.0% |
|
||||
| val_loss | 2.2062 |
|
||||
| 자연 종료율 | 60% |
|
||||
| 짧은 QA 품질 | ✅ 양호 (수도, 김치 등) |
|
||||
| 복잡한 질문 품질 | ❌ 반복 퇴화 심각 |
|
||||
|
||||
### 3B 전환 근거
|
||||
|
||||
1. **반복률 18%는 1B 구조적 한계** — d_model=2048, 24 layers로는 긴 시퀀스에서 hidden state 붕괴 불가피
|
||||
2. **Scaling law 예측**: 3B는 loss ~7% 감소 → 반복률 5~8% 예상
|
||||
3. **ORPO 없이도 목표 달성 가능**: 3B SFT만으로 <10%, +ORPO로 <3%
|
||||
4. **총 소요시간 ORPO 실패→3B (39h) vs 3B 직행 (30h)** — 직행이 빠름
|
||||
|
||||
---
|
||||
|
||||
## 1. 3B 모델 아키텍처
|
||||
|
||||
| 파라미터 | 1B (현재) | 3B (목표) | 변화 |
|
||||
|---------|----------|----------|------|
|
||||
| d_model | 2048 | **3072** | 1.5× |
|
||||
| n_layers | 24 | **32** | 1.33× |
|
||||
| n_heads | 16 | **24** | 1.5× |
|
||||
| n_kv_heads (GQA) | 4 | **8** | 2× (GQA 3:1) |
|
||||
| d_ffn (SwiGLU) | 5472 | **8192** | 1.5× |
|
||||
| vocab_size | 64000 | **64000** | 동일 |
|
||||
| max_seq_len | 4096 | **4096** | 동일 |
|
||||
| rope_theta | 500000 | **500000** | 동일 |
|
||||
| **총 파라미터** | **1.19B** | **~3.42B** | 2.9× |
|
||||
|
||||
### 파라미터 수 상세
|
||||
|
||||
```
|
||||
Embedding: 64000 × 3072 = 196.6M
|
||||
Attention/layer: Q(3072×3072) + K(3072×1024) + V(3072×1024) + O(3072×3072) = 25.1M
|
||||
FFN/layer: SwiGLU gate(3072×8192) + up(3072×8192) + down(8192×3072) = 75.5M
|
||||
Layer total: 25.1 + 75.5 = 100.6M × 32 layers = 3,219M
|
||||
LM Head: tied with embedding
|
||||
총계: 196.6M + 3,219M ≈ 3.42B
|
||||
```
|
||||
|
||||
### GPU 메모리 예상 (8× B200 183GB)
|
||||
|
||||
```
|
||||
모델 (FP8): 3.42 GB
|
||||
Optimizer (FP32): 27.4 GB (DDP 분산 → ~3.4 GB/GPU)
|
||||
Gradients (BF16): 6.84 GB (분산 → ~0.86 GB/GPU)
|
||||
Activations (bs=4): ~15-25 GB (gradient checkpointing)
|
||||
Per GPU 합계: ~28 GB → B200의 15% → 매우 여유
|
||||
```
|
||||
|
||||
### Config 파일
|
||||
|
||||
```yaml
|
||||
# configs/korean_3b_fp8.yaml
|
||||
model:
|
||||
vocab_size: 64000
|
||||
d_model: 3072
|
||||
n_layers: 32
|
||||
n_heads: 24
|
||||
n_kv_heads: 8
|
||||
d_ffn: 8192
|
||||
max_seq_len: 4096
|
||||
rope_theta: 500000.0
|
||||
dropout: 0.0
|
||||
bias: false
|
||||
use_flash_attn: true
|
||||
use_fp8: true
|
||||
|
||||
train:
|
||||
max_steps: 34000 # 8.91B × 4 epoch
|
||||
batch_size: 4
|
||||
grad_accum_steps: 8 # eff_batch: 4 × 8 × 8GPU × 4096 ≈ 1M tok/step
|
||||
lr: 1.5e-4
|
||||
min_lr: 1.5e-5
|
||||
weight_decay: 0.1
|
||||
warmup_steps: 2000
|
||||
max_grad_norm: 1.0
|
||||
log_interval: 10
|
||||
save_interval: 500
|
||||
eval_interval: 200
|
||||
fp8_format: "MXFP8"
|
||||
|
||||
tokenizer:
|
||||
vocab_size: 64000
|
||||
type: sentencepiece_unigram
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 데이터 파이프라인
|
||||
|
||||
### 즉시 사용 가능
|
||||
|
||||
| 소스 | 크기 | 토큰 수 | 상태 |
|
||||
|------|------|---------|------|
|
||||
| korean_c4_train.bin | 15.1 GB | 7.56B | ✅ 토큰화 완료 |
|
||||
| korean_namuwiki_train.bin | 2.2 GB | 1.08B | ✅ 토큰화 완료 |
|
||||
| korean_wiki_train.bin | 0.5 GB | 0.26B | ✅ 토큰화 완료 |
|
||||
| **합계 (korean_train.bin)** | **17.8 GB** | **8.91B** | ✅ **즉시 시작 가능** |
|
||||
|
||||
### 추가 준비 필요 (병렬 토큰화)
|
||||
|
||||
| 소스 | 크기 | 추정 토큰 | 작업 | 예상 소요 |
|
||||
|------|------|----------|------|----------|
|
||||
| culturax_ko | 60 GB | ~30-40B | parquet→토큰화 | 4-6h |
|
||||
| hplt_ko | 23 GB | ~12-15B | 토큰화 | 2-3h |
|
||||
| cc100_ko | 14 GB | ~8-10B | xz해제+토큰화 | 2h |
|
||||
| oscar_ko | 9.2 GB | ~5-6B | 토큰화 | 1-2h |
|
||||
| korean_textbooks | 6.4 GB | ~3-4B | 토큰화 | 1h |
|
||||
| **합계** | **~123 GB** | **~70-80B** | | **8-12h (병렬)** |
|
||||
|
||||
### Chinchilla 분석
|
||||
|
||||
```
|
||||
3.42B × 20 = 68.4B tokens (최적)
|
||||
즉시 사용 가능: 8.91B × 4 epoch = 35.6B (최적의 52%)
|
||||
extra 포함 시: ~80-90B → 충분 (131%)
|
||||
```
|
||||
|
||||
### 데이터 타임라인
|
||||
|
||||
| 시점 | 행동 |
|
||||
|------|------|
|
||||
| **지금** | korean_train.bin 8.91B로 사전학습 시작 (4 epoch) |
|
||||
| **병렬** | korean_extra 토큰화 + MinHash 중복제거 + PPL 필터 진행 |
|
||||
| **Phase 2** | 전체 60-80B 토큰으로 extended pretrain (선택) |
|
||||
|
||||
### SFT 데이터
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 현재 클린 데이터 | ~120-135K 샘플 (필터링 후) |
|
||||
| Val split | 10% (~12-13K) |
|
||||
| 3B에 충분? | ✅ (7B Alpaca도 52K로 학습) |
|
||||
|
||||
### 추가 고품질 SFT 소스 (선택)
|
||||
|
||||
- `hPark/orca-ko` (~200K)
|
||||
- `maywell/synatra-orca` (~300K)
|
||||
- `HAERAE-HUB/qarv-instruct-100k` (100K)
|
||||
- 필터링 후 200-300K 사용 가능
|
||||
|
||||
---
|
||||
|
||||
## 3. 사전학습 계획
|
||||
|
||||
### 하이퍼파라미터
|
||||
|
||||
| 파라미터 | 값 | 근거 |
|
||||
|---------|-----|------|
|
||||
| LR | 1.5e-4 | 3B 표준 (1B의 3e-4 대비 보수적) |
|
||||
| Min LR | 1.5e-5 | LR의 10% |
|
||||
| Warmup | 2,000 steps | ~6% |
|
||||
| Weight Decay | 0.1 | Pretrain 표준 |
|
||||
| Batch Size | 4/GPU × 8GPU × 8 grad_accum = 256 | eff ~1M tok/step |
|
||||
| Max Steps | 34,000 | 8.91B × 4 epoch |
|
||||
| Precision | MXFP8 | B200 최적화 |
|
||||
| Grad Clip | 1.0 | 표준 |
|
||||
|
||||
### 예상 소요 시간
|
||||
|
||||
```
|
||||
1B 실측: ~75,700 tok/s (단일 B200)
|
||||
3B 예상: 파라미터 3× → throughput ~40-50% 감소
|
||||
BUT batch 최적화 + FP8 → 보정
|
||||
|
||||
보수적 추정:
|
||||
8.91B × 4 epoch = 35.6B tokens
|
||||
처리량: ~400K tok/s (8× B200, FP8, 최적 배치)
|
||||
소요: 35.6B / 400K = 89,000초 ≈ 24.7시간
|
||||
|
||||
낙관적 추정:
|
||||
처리량: ~600K tok/s → 16.5시간
|
||||
|
||||
채택 추정: ~26시간
|
||||
```
|
||||
|
||||
### 모니터링
|
||||
|
||||
```bash
|
||||
# 실시간 로그
|
||||
tail -f checkpoints/korean_3b_fp8/train.log
|
||||
|
||||
# TensorBoard
|
||||
tensorboard --logdir checkpoints/korean_3b_fp8/tensorboard --port 6007
|
||||
|
||||
# GPU 상태
|
||||
watch -n 10 nvidia-smi
|
||||
```
|
||||
|
||||
**핵심 관찰 수치**:
|
||||
|
||||
| 수치 | 정상 범위 | 경고 | 즉시 중단 |
|
||||
|------|----------|------|----------|
|
||||
| Train Loss | 시작 ~10, 수렴 ~3-4 | 정체 5000+ steps | 발산 (상승) |
|
||||
| GNorm | 0.5-2.0 | >5.0 | >50 |
|
||||
| PPL | 하강 추세 | 정체 | 상승 |
|
||||
| GPU Util | >90% | <70% | <50% (병목) |
|
||||
| tok/s | >300K | <200K | <100K |
|
||||
|
||||
### 체크포인트 전략
|
||||
|
||||
| Step | 행동 |
|
||||
|------|------|
|
||||
| 500 | Sanity check — loss 하강 중? OOM 없나? |
|
||||
| 5,000 | 1 epoch 완료 — PPL 측정, 한국어 텍스트 perplexity <20? |
|
||||
| 10,000 | 중간점 — loss 추세 확인, 과적합 징후? |
|
||||
| 17,000 | 2 epoch — PPL < 15? |
|
||||
| 25,000 | 3 epoch — PPL < 12? |
|
||||
| 34,000 | 최종 — PPL < 10 목표 |
|
||||
|
||||
**디스크**: 체크포인트 1개 ~27GB (모델 7GB + optimizer 20GB) × save_interval=500 → 68개 = ~1.8TB
|
||||
→ **save_interval=2000으로 변경 권장** → 17개 = ~460GB
|
||||
|
||||
---
|
||||
|
||||
## 4. SFT 계획
|
||||
|
||||
### 1B 교훈 전부 적용
|
||||
|
||||
| 교훈 | 1B에서 발견 | 3B에 적용 |
|
||||
|------|------------|-----------|
|
||||
| Dynamic padding 필수 | 4096 고정으로 90% 낭비 | ✅ sft_dataset.py 수정 완료, 그대로 사용 |
|
||||
| EOS 보존 | 트렁케이션 시 EOS 손실 | ✅ `response_ids[-1] = eos_id` 강제 |
|
||||
| Val split 필수 | 과적합 모니터링 불가했음 | ✅ 10% split |
|
||||
| 3-4 epoch | 2 epoch은 underfitting | ✅ max_steps 계산 |
|
||||
| OpenOrca 과대표집 방지 | 5× 가중치로 과적합 | ✅ 2.0× 이하 |
|
||||
| 데이터 품질 필터 | `</s>` 리터럴, Q/A 마커 오염 | ✅ 필터 스크립트 완성 |
|
||||
| 올바른 포맷 통일 | 학습/추론 포맷 불일치 | ✅ `<\|user\|>/<\|assistant\|>` 일관 |
|
||||
| Early stopping | val_loss 상승해도 학습 계속됨 | ✅ patience=5 구현 |
|
||||
| NEFTune alpha | 10.0은 과도 | ✅ 5.0으로 조정 |
|
||||
|
||||
### 하이퍼파라미터
|
||||
|
||||
| 파라미터 | 값 | 근거 |
|
||||
|---------|-----|------|
|
||||
| LR | 2e-5 | SFT 표준 (Alpaca, Vicuna 동일) |
|
||||
| Warmup | 300 steps | ~3% |
|
||||
| Max Steps | 10,000 | ~3-4 epoch (데이터 크기 따라 조정) |
|
||||
| Batch Size | 4/GPU × 2 grad_accum × 8GPU = 64 | SFT 표준 |
|
||||
| Weight Decay | 0.01 | SFT 표준 (pretrain 0.1보다 낮게) |
|
||||
| NEFTune | alpha=5.0 | 과적합 방지 |
|
||||
| Eval Interval | 500 steps | |
|
||||
| Early Stopping | patience=5 | 2,500 steps 무개선 시 중단 |
|
||||
| Dropout | 0.05 | 과적합 방지 (1B에서 0.0이었음) |
|
||||
|
||||
### 실행 명령어
|
||||
|
||||
```bash
|
||||
RUN_NAME=korean_3b_sft \
|
||||
BASE_CHECKPOINT=checkpoints/korean_3b_fp8/checkpoint-BEST \
|
||||
SFT_DATA=data/sft_v2/train.jsonl \
|
||||
VAL_DATA=data/sft_v2/val.jsonl \
|
||||
MAX_STEPS=10000 \
|
||||
LR=2.0e-5 \
|
||||
WARMUP_STEPS=300 \
|
||||
bash scripts/launch_sft.sh
|
||||
```
|
||||
|
||||
**예상 시간**: ~2시간 (3B는 1B 대비 ~2.5× 느림)
|
||||
|
||||
### 성공 기준
|
||||
|
||||
| 지표 | 목표 | 실패 기준 |
|
||||
|------|------|----------|
|
||||
| Train Loss | < 1.85 | > 2.00 |
|
||||
| Val Loss | Train의 1.1배 이내 | 1.2배 초과 |
|
||||
| 반복률 (raw) | < 10% | > 15% |
|
||||
| 반복률 (rep_penalty=1.1) | < 3% | > 8% |
|
||||
| EOS 종료율 | > 80% | < 60% |
|
||||
|
||||
---
|
||||
|
||||
## 5. ORPO 계획
|
||||
|
||||
### 타이밍: SFT 완료 후, 반복률 >5%일 때만
|
||||
|
||||
### 데이터
|
||||
|
||||
| 소스 | 샘플 수 | 유형 |
|
||||
|------|---------|------|
|
||||
| maywell/ko_Ultrafeedback_binarized | ~60K | 일반 도메인 preference |
|
||||
| kuotient/orca-math-korean-dpo-pairs | 수천 | 수학 도메인 |
|
||||
| **자체 생성** (3B SFT 모델로) | ~2-5K | 반복 타겟 preference |
|
||||
|
||||
**자체 생성 방법**:
|
||||
1. 3B SFT 모델로 1000 프롬프트 × 4 temperature 생성
|
||||
2. 반복 출력 → rejected, 깨끗한 출력 → chosen
|
||||
3. 3B에서는 반복률 낮으므로 **1B보다 훨씬 편향 적음**
|
||||
|
||||
### 하이퍼파라미터
|
||||
|
||||
```python
|
||||
ORPOConfig(
|
||||
learning_rate=5e-7, # 매우 낮은 LR (정렬 학습)
|
||||
num_train_epochs=1, # 1 epoch 충분
|
||||
per_device_train_batch_size=4,
|
||||
gradient_accumulation_steps=4,
|
||||
beta=0.1, # ORPO coefficient
|
||||
)
|
||||
```
|
||||
|
||||
### 예상 시간: 1-2시간
|
||||
|
||||
### 목표: 반복률 <3% (raw), <1% (rep_penalty=1.1)
|
||||
|
||||
---
|
||||
|
||||
## 6. 평가 계획
|
||||
|
||||
### 벤치마크
|
||||
|
||||
| 벤치마크 | 도구 | 1B 예상 | 3B 목표 |
|
||||
|---------|------|---------|---------|
|
||||
| ko_ifeval | lm-eval-harness | 15-25% | **35-45%** |
|
||||
| ko_winogrande | lm-eval-harness | 53-58% | **60-68%** |
|
||||
| KoBEST (5 tasks avg) | lm-eval-harness | 55-60% | **65-75%** |
|
||||
| 반복률 (raw) | test_generation_params.py | 18% | **<8%** |
|
||||
| 반복률 (+rep_penalty) | test_generation_params.py | ~5-8% | **<3%** |
|
||||
|
||||
### 실행 명령어
|
||||
|
||||
```bash
|
||||
# ko_ifeval
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_3b_sft/checkpoint-BEST,dtype=bfloat16 \
|
||||
--tasks ko_ifeval --device cuda:0
|
||||
|
||||
# KoBEST
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_3b_sft/checkpoint-BEST,dtype=bfloat16 \
|
||||
--tasks kobest_boolq,kobest_copa,kobest_wic,kobest_hellaswag,kobest_sentineg \
|
||||
--device cuda:0
|
||||
```
|
||||
|
||||
### 판단 기준
|
||||
|
||||
```
|
||||
[3B SFT 평가 결과]
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
✅ PASS ⚠️ PARTIAL ❌ FAIL
|
||||
반복률<5% 반복률 5-10% 반복률>10%
|
||||
ko_ifeval>35% ko_ifeval 25-35% ko_ifeval<25%
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
배포 준비 ORPO 적용 원인 분석
|
||||
(Phase 배포) (Phase 5) (데이터/아키텍처 점검)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 전체 타임라인
|
||||
|
||||
### 한눈에 보기
|
||||
|
||||
```
|
||||
Day 0 (지금, 04:30)
|
||||
├── [04:30] Config 작성 + sanity check 30분
|
||||
├── [05:00] 🔥 사전학습 시작 ← 오늘 밤 시작
|
||||
├── [05:00] (병렬) korean_extra 토큰화 시작 8-12h
|
||||
│
|
||||
Day 1 (내일)
|
||||
├── [~07:00] 사전학습 진행 중... (~26시간)
|
||||
├── [중간] 체크포인트 PPL 확인
|
||||
│
|
||||
Day 1.5
|
||||
├── [~07:00+26h = Day 2 07:00] 사전학습 완료
|
||||
├── [07:00] SFT 시작 2시간
|
||||
├── [09:00] SFT 완료 → 평가
|
||||
├── [09:30] 반복률 <5%? → 배포
|
||||
├── [09:30] 반복률 5-10%? → ORPO 1-2시간
|
||||
├── [11:30] ORPO 완료 → 최종 평가
|
||||
│
|
||||
Day 2
|
||||
├── 벤치마크 풀 스위트 2시간
|
||||
├── HuggingFace 업로드 1시간
|
||||
├── vLLM 서빙 테스트 1시간
|
||||
└── 🎉 배포 완료
|
||||
```
|
||||
|
||||
### 표 형식
|
||||
|
||||
| 단계 | 시작 | 소요 | 완료 | 의존성 |
|
||||
|------|------|------|------|--------|
|
||||
| **0. Config + Sanity** | Day 0 04:30 | 30분 | 05:00 | 없음 |
|
||||
| **1. 사전학습** | Day 0 05:00 | **26시간** | Day 1 ~07:00 | Config |
|
||||
| **(병렬) Extra 토큰화** | Day 0 05:00 | 8-12시간 | Day 0 ~17:00 | 없음 |
|
||||
| **2. SFT** | Day 1 07:00 | **2시간** | Day 1 09:00 | 사전학습 완료 |
|
||||
| **3. 1차 평가** | Day 1 09:00 | 30분 | Day 1 09:30 | SFT 완료 |
|
||||
| **4. ORPO (조건부)** | Day 1 09:30 | 1-2시간 | Day 1 11:30 | 반복률 >5% |
|
||||
| **5. 풀 벤치마크** | Day 1 11:30 | 2시간 | Day 1 13:30 | |
|
||||
| **6. 배포** | Day 1 13:30 | 2시간 | Day 1 15:30 | 벤치마크 통과 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 의사결정 트리
|
||||
|
||||
### Phase 1: 사전학습 중 (Step 5000, 10000, ...)
|
||||
|
||||
```
|
||||
Loss 하강 중?
|
||||
├── YES → 계속
|
||||
└── NO (정체 3000+ steps)
|
||||
├── 데이터 품질 문제? → PPL 필터 강화 + 재시작
|
||||
├── LR 문제? → LR 반감 후 resume
|
||||
└── 모델 아키텍처? → d_model/n_layers 조정 (최후 수단)
|
||||
|
||||
PPL (한국어 텍스트)?
|
||||
├── < 15 at 2 epoch → 정상
|
||||
├── 15-20 at 2 epoch → 주의 (데이터 부족?)
|
||||
└── > 20 at 2 epoch → 문제 (데이터 품질 or 하이퍼파라미터)
|
||||
|
||||
OOM?
|
||||
├── batch_size 4→2, grad_accum 8→16
|
||||
└── gradient checkpointing 확인
|
||||
```
|
||||
|
||||
### Phase 2: SFT 후
|
||||
|
||||
```
|
||||
반복률 (raw)?
|
||||
├── < 5% → ✅ 배포 가능! (ORPO 건너뜀)
|
||||
├── 5-10% → ⚠️ ORPO 적용
|
||||
├── 10-15% → 🟠 SFT 하이퍼파라미터 조정 후 재시도
|
||||
└── > 15% → ❌ 사전학습 품질 문제 → Phase 1 재점검
|
||||
|
||||
ko_ifeval?
|
||||
├── > 35% → ✅ 목표 달성
|
||||
├── 25-35% → 🟡 데이터 augmentation 고려
|
||||
└── < 25% → 🔴 3B에서도 이러면 데이터 문제 심각
|
||||
```
|
||||
|
||||
### Phase 3: ORPO 후
|
||||
|
||||
```
|
||||
반복률?
|
||||
├── < 3% → ✅ 완료
|
||||
├── 3-5% → 🟡 서빙 시 rep_penalty=1.05로 보완
|
||||
└── > 5% → 🔴 preference 데이터 재검토
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 예외 대응
|
||||
|
||||
| 시나리오 | 확률 | 대응 |
|
||||
|---------|------|------|
|
||||
| **OOM** | 5% | batch_size 4→2, grad_accum 2× |
|
||||
| **Loss 발산** | 5% | LR 반감, grad_clip 0.5로 강화 |
|
||||
| **GPU Hang / NCCL** | 10% | `pkill torchrun` → latest checkpoint에서 resume |
|
||||
| **디스크 부족** | 3% | save_interval 2000→5000, 오래된 ckpt 삭제 |
|
||||
| **사전학습 후 PPL >20** | 10% | 데이터 추가 (korean_extra) + extended training |
|
||||
| **SFT 후 반복률 >15%** | 10% | 데이터 필터 재강화 + LR/epoch 조정 |
|
||||
| **ORPO 후 품질 퇴행** | 15% | ORPO LR 낮추기 (5e-7 → 1e-7), beta 조정 |
|
||||
| **FP8 수치 불안정** | 5% | BF16으로 폴백 (시간 1.5× 증가) |
|
||||
|
||||
### NCCL/GPU 복구 스크립트
|
||||
|
||||
```bash
|
||||
# 프로세스 정리
|
||||
pkill -f torchrun && sleep 5
|
||||
|
||||
# 최신 체크포인트 찾기
|
||||
LATEST=$(ls -d checkpoints/korean_3b_fp8/checkpoint-[0-9]* 2>/dev/null \
|
||||
| sort -t- -k2 -n | tail -1)
|
||||
|
||||
# 재시작
|
||||
bash scripts/run_pretrain.sh --config configs/korean_3b_fp8.yaml --resume "${LATEST}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 1B에서 배운 교훈 체크리스트
|
||||
|
||||
### 학습 전 필수 확인
|
||||
|
||||
- [ ] **Dynamic padding 작동 확인**: `SFTDataset.__getitem__`이 가변 길이 텐서 반환, `dynamic_collate_fn` 배치별 패딩
|
||||
- [ ] **EOS 보존 확인**: `grep -n "eos_token_id" data/sft_dataset.py` — 트렁케이션 시 강제 부착
|
||||
- [ ] **Val split 존재**: `wc -l data/sft_v2/val.jsonl` → 10K+ 확인
|
||||
- [ ] **데이터 오염 제거**: `</s>` 리터럴, Q/A 마커, 자체 반복 패턴 필터 적용됨
|
||||
- [ ] **OpenOrca 가중치 ≤ 2.0**: prepare_sft_data.py에서 확인
|
||||
- [ ] **프롬프트 포맷 통일**: 학습 = 추론 = `<|user|>/<|assistant|>`
|
||||
- [ ] **Labels shift 정상**: trainer.py에서 `logits[t]` → `targets[t]` 직접 비교, labels에서 shift 처리
|
||||
|
||||
### 학습 중 필수 모니터링
|
||||
|
||||
- [ ] **Val loss 추적**: 매 eval_interval마다 기록, 3연속 상승 시 주의
|
||||
- [ ] **Early stopping 활성화**: patience=5
|
||||
- [ ] **Loss 0 감지**: 3 step 연속 loss < 0.01 → labels 버그 즉시 확인
|
||||
- [ ] **Grad norm**: > 10이면 경고, > 50이면 중단
|
||||
|
||||
### 학습 후 필수 확인
|
||||
|
||||
- [ ] **올바른 포맷으로 생성 테스트**: `<|user|>\n{질문}\n<|assistant|>\n`
|
||||
- [ ] **rep_penalty 없이 반복률 측정**: 목표 <10%
|
||||
- [ ] **rep_penalty=1.1로 반복률**: 목표 <3%
|
||||
- [ ] **벤치마크 실행**: ko_ifeval, KoBEST
|
||||
|
||||
### 절대 반복하지 말 것
|
||||
|
||||
- ❌ 학습/추론 포맷 불일치 상태로 평가하지 말 것
|
||||
- ❌ Val split 없이 학습하지 말 것
|
||||
- ❌ 특정 소스 5× 이상 업샘플링하지 말 것
|
||||
- ❌ 2 epoch 미만으로 학습하지 말 것
|
||||
- ❌ Dynamic padding 미작동 상태로 학습하지 말 것
|
||||
- ❌ 반복률 측정 없이 "loss 낮으니 OK" 판단하지 말 것
|
||||
|
||||
---
|
||||
|
||||
## 🔥 오늘 밤 지금 당장 시작할 첫 번째 명령어
|
||||
|
||||
```bash
|
||||
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
|
||||
|
||||
# 1. 3B config 작성
|
||||
cat > configs/korean_3b_fp8.yaml << 'YAML'
|
||||
model:
|
||||
vocab_size: 64000
|
||||
d_model: 3072
|
||||
n_layers: 32
|
||||
n_heads: 24
|
||||
n_kv_heads: 8
|
||||
d_ffn: 8192
|
||||
max_seq_len: 4096
|
||||
rope_theta: 500000.0
|
||||
dropout: 0.0
|
||||
bias: false
|
||||
use_flash_attn: true
|
||||
use_fp8: true
|
||||
train:
|
||||
max_steps: 34000
|
||||
batch_size: 4
|
||||
grad_accum_steps: 8
|
||||
lr: 1.5e-4
|
||||
min_lr: 1.5e-5
|
||||
weight_decay: 0.1
|
||||
warmup_steps: 2000
|
||||
max_grad_norm: 1.0
|
||||
log_interval: 10
|
||||
save_interval: 2000
|
||||
eval_interval: 500
|
||||
fp8_format: "MXFP8"
|
||||
YAML
|
||||
|
||||
# 2. 사전학습 시작!
|
||||
bash scripts/run_pretrain.sh --config configs/korean_3b_fp8.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 가장 중요한 판단 포인트 3개
|
||||
|
||||
### 1️⃣ 사전학습 Step 5,000 (1 epoch 완료) — "기초 체력 확인"
|
||||
- **PPL < 20?** → 정상, 계속
|
||||
- **PPL > 20?** → 데이터 품질 or 하이퍼파라미터 문제. 즉시 진단
|
||||
|
||||
### 2️⃣ SFT 후 반복률 측정 — "3B의 진짜 실력"
|
||||
- **<5%?** → 🎉 ORPO 없이 바로 배포. 대성공
|
||||
- **5-10%?** → ORPO 1라운드로 해결 가능
|
||||
- **>10%?** → 사전학습 품질 재검토 필요 (이 확률은 낮음)
|
||||
|
||||
### 3️⃣ ko_ifeval 점수 — "실사용 가능 수준?"
|
||||
- **>35%?** → 3B 한국어 모델로서 경쟁력 있음. 배포
|
||||
- **25-35%?** → 추가 SFT 데이터로 개선 여지 있음
|
||||
- **<25%?** → 사전학습 데이터가 부족했을 가능성 → extended pretrain 고려
|
||||
|
||||
---
|
||||
|
||||
*"1B에서 배웠고, 3B에서 증명한다."*
|
||||
|
||||
*이 문서는 각 Phase 완료 시 실측 결과로 업데이트할 것.*
|
||||
162
source/eval/plan/3b_data_pipeline.md
Normal file
162
source/eval/plan/3b_data_pipeline.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# 3B 사전학습 데이터 파이프라인
|
||||
|
||||
**작성일:** 2026-02-27
|
||||
**프로젝트:** `/PROJECT/0325120031_A/ghong/taketimes/llm-bang/`
|
||||
|
||||
---
|
||||
|
||||
## 1. 현재 데이터 현황
|
||||
|
||||
### 토큰화 완료 (즉시 사용 가능)
|
||||
|
||||
| 파일 | 크기 | 토큰 수 |
|
||||
|------|------|---------|
|
||||
| korean_c4_train.bin | 15 GB | **7.56B** |
|
||||
| korean_namuwiki_train.bin | 2.1 GB | **1.08B** |
|
||||
| korean_wiki_train.bin | 500 MB | **262M** |
|
||||
| korean_train.bin (위 3개 병합) | 17 GB | **9.0B** |
|
||||
| train.bin (영어 기타) | 1.2 GB | **606M** |
|
||||
|
||||
**즉시 사용 가능: ~9.6B 토큰**
|
||||
|
||||
### 미토큰화 원본 (korean_extra/)
|
||||
|
||||
| 소스 | 디스크 크기 | 형식 | 예상 토큰 수 | 비고 |
|
||||
|------|------------|------|-------------|------|
|
||||
| CulturaX (ko) | 60 GB (32 parquet) | parquet, `text` 컬럼 | **~11.6B** | 고품질 웹 + mC4 중복제거 |
|
||||
| cc100 (ko) | 14 GB xz → 54 GB text | xz 압축 텍스트 | **~13.5B** | 줄 단위 텍스트 |
|
||||
| HPLT (en-ko) | 23 GB (193 parquet) | 병렬 코퍼스, `tgt_doc.sentences` | **~3.7B** | 한국어 측만 추출 필요 |
|
||||
| OSCAR (ko) | 9.2 GB (27 parquet) | 중첩 구조, `text[].text` | **~2.0B** | 웹 텍스트 |
|
||||
| korean_webtext | 4.2 GB (18 parquet) | parquet | **~1.5B** | 한국어 웹 |
|
||||
| korean_textbooks | 6.4 GB | MMLU 스타일 parquet | **~0.5B** | 교과서/시험 (구조적) |
|
||||
| finepdfs_edu_ko | 2.9 GB (parquet) | parquet | **~0.8B** | 교육 PDF |
|
||||
| namuwiki_extracted | 2.2 GB | 텍스트 | **~0.6B** | 이미 namuwiki_train.bin과 중복 가능 |
|
||||
| kovast | 449 MB | - | **~0.1B** | 소량 |
|
||||
| korean_safe_conv | 51 MB | jsonl | **~15M** | 대화 데이터 |
|
||||
| evol_instruct_ko | 144 MB | - | **~40M** | SFT용, pretrain 부적합 |
|
||||
|
||||
**미토큰화 총 예상: ~34B 토큰**
|
||||
**전체 합계: ~43B 토큰 (중복 제거 전)**
|
||||
|
||||
---
|
||||
|
||||
## 2. 3B 모델 학습 목표
|
||||
|
||||
Chinchilla 스케일링 법칙에 따른 최적:
|
||||
- **최소:** 3B × 20 = **60B 토큰**
|
||||
- **최적:** 3B × 50 = **150B 토큰**
|
||||
- **현실적 목표:** 가용 데이터 ~35B 토큰 (중복 제거 후) → **2~3 epoch 반복으로 60-100B 토큰**
|
||||
|
||||
### 데이터 믹싱 전략
|
||||
|
||||
| 카테고리 | 소스 | 비율 | 이유 |
|
||||
|----------|------|------|------|
|
||||
| 고품질 웹 | CulturaX | 35% | 이미 중복 제거된 고품질 |
|
||||
| 대규모 웹 | cc100 + mC4(기존) | 35% | 양적 확보 |
|
||||
| 백과사전 | 위키 + 나무위키 | 10% | 사실 지식 |
|
||||
| 보조 웹 | OSCAR + korean_webtext + HPLT | 15% | 다양성 |
|
||||
| 전문 도메인 | textbooks + finepdfs | 5% | 교육 품질 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 품질 필터링 계획
|
||||
|
||||
### Phase 1: 기본 필터 (빠름, 1-2시간)
|
||||
- **언어 필터:** `langdetect`로 한국어 비율 < 50% 문서 제거
|
||||
- HPLT: 병렬 코퍼스라 한국어 추출만 하면 됨
|
||||
- cc100: 이미 한국어지만 혼입 확인
|
||||
- **길이 필터:** 50자 미만 문서 제거
|
||||
- **중복 줄 제거:** 같은 줄 5회 이상 반복하는 문서 제거
|
||||
|
||||
### Phase 2: MinHash 중복 제거 (4-8시간)
|
||||
```
|
||||
도구: datasketch (pip install datasketch)
|
||||
방법: 5-gram MinHash, 128 permutations
|
||||
임계값: Jaccard > 0.8 → 중복
|
||||
예상 제거율: 15-25% (특히 cc100 + CulturaX 간)
|
||||
```
|
||||
- CulturaX는 이미 내부 중복 제거됨 → 다른 소스와의 교차 중복만 체크
|
||||
- 72코어 병렬 처리로 ~4시간 예상
|
||||
|
||||
### Phase 3: Perplexity 필터 (선택, 12-24시간)
|
||||
- 현재 1B 모델로 각 문서 perplexity 계산
|
||||
- 하위 5% (너무 쉬움 = 템플릿/반복) + 상위 5% (노이즈) 제거
|
||||
- **권장: 3B 첫 학습 후 2차 학습 시 적용** (시간 절약)
|
||||
|
||||
---
|
||||
|
||||
## 4. 토큰화 파이프라인
|
||||
|
||||
### 우선순위 및 예상 시간 (72코어 기준)
|
||||
|
||||
| 순위 | 소스 | 예상 시간 | 이유 |
|
||||
|------|------|----------|------|
|
||||
| 1 | CulturaX | 3-4시간 | 60GB parquet, 가장 크고 고품질 |
|
||||
| 2 | cc100 | 2-3시간 | xz 해제 30분 + 토큰화 2시간 |
|
||||
| 3 | OSCAR | 1시간 | 9.2GB, 구조 파싱 필요 |
|
||||
| 4 | korean_webtext | 30분 | 4.2GB |
|
||||
| 5 | HPLT (Korean) | 1-2시간 | 한국어 추출 + 토큰화 |
|
||||
| 6 | textbooks + finepdfs | 30분 | 소량 |
|
||||
|
||||
**총 예상: 8-12시간 (병렬 처리 시)**
|
||||
|
||||
---
|
||||
|
||||
## 5. 타임라인
|
||||
|
||||
### 최소 시작 방안 (즉시, 0시간)
|
||||
- 기존 `korean_train.bin` (9B 토큰)으로 3B 학습 시작 가능
|
||||
- 1 epoch에 부족하지만 학습 코드 검증 + 초기 학습에는 충분
|
||||
|
||||
### Phase A: 빠른 확장 (8-12시간)
|
||||
1. CulturaX 토큰화 (3-4시간)
|
||||
2. cc100 해제 + 토큰화 (2-3시간, CulturaX와 병렬 가능)
|
||||
3. 기타 소스 토큰화 (2시간)
|
||||
4. 병합 → ~35B 토큰
|
||||
|
||||
### Phase B: 품질 필터링 (추가 4-8시간)
|
||||
1. 기본 필터 (1-2시간)
|
||||
2. MinHash 중복 제거 (4-8시간)
|
||||
3. 최종 병합 → ~28-30B 토큰 (깨끗한 데이터)
|
||||
|
||||
### Phase C: 학습
|
||||
- 30B 토큰 × 2 epoch = 60B 토큰 (Chinchilla 최소)
|
||||
- 또는 30B × 3 epoch = 90B 토큰 (안전)
|
||||
|
||||
---
|
||||
|
||||
## 6. 파일 구조 (최종)
|
||||
|
||||
```
|
||||
data/
|
||||
├── korean_train.bin # 기존 9B (c4+wiki+namuwiki)
|
||||
├── korean_val.bin
|
||||
├── culturax_train.bin # ~11.6B
|
||||
├── culturax_val.bin
|
||||
├── cc100_train.bin # ~13.5B
|
||||
├── cc100_val.bin
|
||||
├── oscar_train.bin # ~2B
|
||||
├── oscar_val.bin
|
||||
├── webtext_train.bin # ~1.5B
|
||||
├── webtext_val.bin
|
||||
├── hplt_ko_train.bin # ~3.7B
|
||||
├── hplt_ko_val.bin
|
||||
├── extra_train.bin # textbooks + finepdfs + kovast
|
||||
├── extra_val.bin
|
||||
├── merged_3b_train.bin # 전체 병합 (~35B)
|
||||
└── merged_3b_val.bin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 즉시 사용 가능 토큰 | **9.6B** |
|
||||
| 추가 토큰화 후 | **~35B** (중복 제거 전) |
|
||||
| 중복 제거 후 예상 | **~28-30B** |
|
||||
| 3B Chinchilla 최소 | 60B (2 epoch) |
|
||||
| 데이터 준비 최소 시간 | **0시간** (기존 데이터 사용) |
|
||||
| 전체 파이프라인 완료 | **12-20시간** |
|
||||
| 학습 시작 추천 | 기존 9B로 즉시 시작 + CulturaX 병렬 준비 |
|
||||
223
source/eval/plan/3b_training_pipeline.md
Normal file
223
source/eval/plan/3b_training_pipeline.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# 3B Korean LLM 학습 파이프라인 — 전체 계획
|
||||
|
||||
**작성일:** 2026-02-27
|
||||
**서버:** 8× B200 192GB, NVSwitch, CUDA 13.1, PyTorch 2.10, TransformerEngine FP8
|
||||
**프로젝트:** `/PROJECT/0325120031_A/ghong/taketimes/llm-bang/`
|
||||
|
||||
---
|
||||
|
||||
## 1. 모델 아키텍처 (3B)
|
||||
|
||||
| 항목 | 1B (현재) | 3B (신규) | 근거 |
|
||||
|------|-----------|-----------|------|
|
||||
| d_model | 2048 | 3072 | LLaMA-3 3B 참고 |
|
||||
| n_layers | 24 | 28 | |
|
||||
| n_heads | 16 | 24 | |
|
||||
| n_kv_heads | 4 (GQA 4:1) | 8 (GQA 3:1) | 더 큰 모델에서 KV 좀 더 여유 |
|
||||
| d_ffn | 5472 | 8192 | ~2.67× d_model, 128배수 (FP8) |
|
||||
| max_seq_len | 4096 | 4096 | 동일 |
|
||||
| vocab_size | 64000 | 64000 | 동일 토크나이저 |
|
||||
| 총 파라미터 | ~1.0B | ~3.0B | |
|
||||
|
||||
## 2. 사전학습 하이퍼파라미터
|
||||
|
||||
| 항목 | 1B 값 | 3B 값 | 근거 |
|
||||
|------|-------|-------|------|
|
||||
| **Learning Rate** | 2e-4 | **1.5e-4** | μP scaling ~1/√(3), LLaMA-3 3B 참고 |
|
||||
| **LR Schedule** | cosine decay | cosine decay | 동일 |
|
||||
| **Warmup Steps** | 2000 | 2000 | 57k의 3.5% (적절) |
|
||||
| **Weight Decay** | 0.1 | 0.1 | 표준 |
|
||||
| **Gradient Clip** | 1.0 | 1.0 | 표준 |
|
||||
| **Batch Size (local)** | 8 | 8 | per GPU |
|
||||
| **Grad Accum** | 4 | 4 | |
|
||||
| **Eff Batch** | 1M tok/step | 1M tok/step | 8×8×4×4096 |
|
||||
| **Max Steps** | 34,000 | **57,000** (60B tok) | Chinchilla: 3B → 60B min |
|
||||
| **총 토큰** | 35.6B | **60B** (최소) / 95k steps=100B (권장) | |
|
||||
| **Save Interval** | 500 | **2000** | 27GB/체크포인트 → 덜 자주 |
|
||||
| **Eval Interval** | 200 | **500** | |
|
||||
| **FP8** | MXFP8 | MXFP8 | B200 네이티브 |
|
||||
|
||||
## 3. 자원 예측
|
||||
|
||||
### 체크포인트
|
||||
- model.pt: 3B × 1B(FP8) ≈ **3GB**
|
||||
- optimizer.pt: 3B × 8B(FP32 states) ≈ **24GB**
|
||||
- 체크포인트당 **~27GB**
|
||||
- 2000 step 간격 → 최대 ~28개 = **756GB**
|
||||
- /PROJECT 여유 19TB → **충분**
|
||||
|
||||
### VRAM
|
||||
- 모델: ~6GB (FP8)
|
||||
- Optimizer states: ~24GB
|
||||
- Activations: ~40-60GB (batch 8, seq 4096)
|
||||
- 총: ~80-90GB/GPU → B200 192GB로 **충분** (47% 사용)
|
||||
|
||||
### 학습 시간 예상
|
||||
- 1B: 34k steps → ~12h (관찰값 기반)
|
||||
- 3B: step당 ~3배 → step ~1.05s 예상
|
||||
- 57k steps × 1.05s ≈ **~17h** (60B tokens)
|
||||
- 95k steps × 1.05s ≈ **~28h** (100B tokens)
|
||||
- 안전 마진 포함: **24~36h**
|
||||
|
||||
## 4. NCCL 최적화 (B200 NVSwitch)
|
||||
|
||||
```bash
|
||||
export NCCL_IB_DISABLE=1 # 단일 노드, IB 불필요
|
||||
export NCCL_ALGO=Ring,Tree # 3B gradient 크기에 두 알고리즘 병행
|
||||
export NCCL_PROTO=Simple # NVLink bulk transfer 최적
|
||||
export NCCL_MIN_NCHANNELS=16
|
||||
export NCCL_MAX_NCHANNELS=16
|
||||
export NCCL_BUFFSIZE=134217728 # 128MB (1B의 64MB에서 증가)
|
||||
export NCCL_P2P_LEVEL=NVL # NVLink 직접 P2P
|
||||
```
|
||||
|
||||
## 5. SFT 계획 (사전학습 완료 후)
|
||||
|
||||
### 데이터
|
||||
- 1B SFT 데이터 (161k 샘플) — 기존 검증 완료
|
||||
- 추가 고품질 데이터 고려:
|
||||
- Ko-Alpaca 확장
|
||||
- ShareGPT-ko 추가
|
||||
- 목표: **200k+ 샘플**
|
||||
|
||||
### 하이퍼파라미터
|
||||
|
||||
| 항목 | 1B SFT | 3B SFT | 근거 |
|
||||
|------|--------|--------|------|
|
||||
| LR | 2e-5 | **1e-5** | 더 큰 모델 → 더 낮은 LR |
|
||||
| Batch (local) | 4 | 4 | |
|
||||
| Grad Accum | 2 | 2 | |
|
||||
| Eff Batch | 64 | 64 | |
|
||||
| Max Steps | 9000 | **12000** | 3B는 수렴에 좀 더 필요 |
|
||||
| Warmup | 300 | 500 | |
|
||||
| Max Seq Len | 4096 | 4096 | |
|
||||
|
||||
### 1B SFT 교훈 반영
|
||||
- ✅ Labels shift 버그 — sft_dataset.py 이미 수정됨
|
||||
- ✅ 프로세스 중복 방지 — launch 스크립트에 pgrep 체크 추가
|
||||
- ✅ Loss 0 감지 — 모니터링에 포함
|
||||
|
||||
### 예상 SFT 시간
|
||||
- 12000 steps × ~0.8s/step ≈ **~3h**
|
||||
|
||||
## 6. ORPO 계획 (SFT 완료 후)
|
||||
|
||||
### 데이터
|
||||
- **`maywell/ko_Ultrafeedback_binarized`** — Korean preference 데이터
|
||||
- 다운로드:
|
||||
```python
|
||||
from datasets import load_dataset
|
||||
ds = load_dataset("maywell/ko_Ultrafeedback_binarized")
|
||||
```
|
||||
|
||||
### 설정
|
||||
| 항목 | 값 | 근거 |
|
||||
|------|-----|------|
|
||||
| LR | 5e-6 | ORPO 표준, SFT보다 낮게 |
|
||||
| β (ORPO lambda) | 0.1 | 논문 기본값 |
|
||||
| Batch (local) | 2 | chosen+rejected 쌍 → 메모리 2배 |
|
||||
| Grad Accum | 4 | eff_batch = 64 |
|
||||
| Max Steps | ~3000-5000 | 데이터 크기에 따라 |
|
||||
| Max Seq Len | 4096 | |
|
||||
|
||||
### 예상 ORPO 시간
|
||||
- 5000 steps × ~1.5s/step (쌍 비교) ≈ **~2h**
|
||||
|
||||
## 7. 전체 타임라인
|
||||
|
||||
```
|
||||
Phase 1: 사전학습 (60B tokens)
|
||||
├─ 준비: configs, scripts 확인 ~30분
|
||||
├─ 학습: 57k steps ~24-36h
|
||||
└─ 평가: eval suite ~1h
|
||||
|
||||
Phase 2: SFT
|
||||
├─ 데이터 준비: 161k+ 검증 ~30분
|
||||
├─ 학습: 12k steps ~3h
|
||||
└─ 평가 ~30분
|
||||
|
||||
Phase 3: ORPO
|
||||
├─ 데이터 다운로드+처리 ~30분
|
||||
├─ 학습: 3-5k steps ~2h
|
||||
└─ 최종 평가 ~1h
|
||||
|
||||
총 예상: 약 3-4일 (사전학습 포함)
|
||||
사전학습만: 24-36h
|
||||
SFT+ORPO: ~6h
|
||||
```
|
||||
|
||||
## 8. 예외 대응 플레이북 (3B 특화)
|
||||
|
||||
### 8-1. 서버 재시작이 필요한 경우
|
||||
1. `Ctrl+C`로 graceful stop (SIGINT → 현재 step 완료 후 체크포인트 저장)
|
||||
2. 안 되면 `kill -15 <PID>` → 10초 대기 → `kill -9`
|
||||
3. 재시작 후: `bash scripts/launch_3b_pretrain.sh` (자동 resume 감지)
|
||||
|
||||
### 8-2. 체크포인트 손상
|
||||
```bash
|
||||
# 최근 체크포인트 무결성 확인
|
||||
python -c "
|
||||
import torch
|
||||
ckpt = torch.load('checkpoints/korean_3b_fp8_run1/checkpoint-XXXXX/model.pt', weights_only=True)
|
||||
print(f'Keys: {len(ckpt)}')
|
||||
print(f'Total params: {sum(v.numel() for v in ckpt.values()):,}')
|
||||
"
|
||||
# 손상 시 → 이전 체크포인트로 resume
|
||||
bash scripts/launch_3b_pretrain.sh --resume checkpoints/korean_3b_fp8_run1/checkpoint-YYYYY
|
||||
```
|
||||
|
||||
### 8-3. NCCL Hang 감지
|
||||
- `monitor_3b.sh` 로그 10분 멈춤 → CRITICAL 알림
|
||||
- `--auto-restart` 옵션으로 자동 kill + 재시작 가이드
|
||||
- 수동: `kill -9 $(pgrep -f pretrain.py)` → 재실행
|
||||
|
||||
### 8-4. 디스크 공간 부족
|
||||
- `monitor_3b.sh --auto-cleanup`: MAX_CHECKPOINTS(15) 초과 시 오래된 것 자동 삭제
|
||||
- 수동 정리:
|
||||
```bash
|
||||
ls -d checkpoints/korean_3b_fp8_run1/checkpoint-* | sort -V | head -10 | xargs rm -rf
|
||||
```
|
||||
|
||||
### 8-5. Loss 발산 (NaN / spike)
|
||||
1. 즉시 중단
|
||||
2. 최근 정상 체크포인트에서 resume
|
||||
3. LR 50% 감소하여 재시작: `--lr 7.5e-5`
|
||||
4. 반복 시 warmup 늘리기: `--warmup_steps 4000`
|
||||
|
||||
## 9. 모니터링 스크립트
|
||||
|
||||
### 실시간 감시
|
||||
```bash
|
||||
bash scripts/monitor_3b.sh # 기본 (60초 간격)
|
||||
bash scripts/monitor_3b.sh --check-once # 1회
|
||||
bash scripts/monitor_3b.sh --auto-cleanup # 자동 체크포인트 정리
|
||||
bash scripts/monitor_3b.sh --auto-restart # NCCL hang 시 자동 kill
|
||||
```
|
||||
|
||||
### TensorBoard
|
||||
```bash
|
||||
tensorboard --logdir checkpoints/korean_3b_fp8_run1/tensorboard --port 6007
|
||||
```
|
||||
|
||||
## 10. 실행 커맨드 요약
|
||||
|
||||
```bash
|
||||
# Step 1: 사전학습 시작
|
||||
bash scripts/launch_3b_pretrain.sh
|
||||
|
||||
# Step 1b: 모니터링 (별도 터미널)
|
||||
bash scripts/monitor_3b.sh --auto-cleanup
|
||||
|
||||
# Step 2: 사전학습 완료 후 SFT (launch_sft.sh 3B 버전 필요)
|
||||
BASE_CHECKPOINT=checkpoints/korean_3b_fp8_run1/checkpoint-XXXXX \
|
||||
RUN_NAME=korean_3b_sft \
|
||||
bash scripts/launch_sft.sh --lr 1e-5 --max_steps 12000 --warmup_steps 500
|
||||
|
||||
# Step 3: ORPO (별도 스크립트 필요 — train/orpo.py 작성 필요)
|
||||
# TBD after SFT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**상태:** ✅ 파이프라인 설계 완료, 스크립트 작성 완료
|
||||
**다음 액션:** `bash scripts/launch_3b_pretrain.sh` 실행
|
||||
601
source/eval/plan/MASTER_PLAN.md
Normal file
601
source/eval/plan/MASTER_PLAN.md
Normal file
@@ -0,0 +1,601 @@
|
||||
# 🗺️ MASTER PLAN: 한국어 LLM 1B 재학습 → 3B → 배포
|
||||
|
||||
**작성일**: 2026-02-27
|
||||
**프로젝트**: `/PROJECT/0325120031_A/ghong/taketimes/llm-bang/`
|
||||
**결정**: Restart (base checkpoint에서 클린 재학습)
|
||||
**총 예상 기간**: ~35시간 (1B: 3시간 → 3B pretrain: 26시간 → 3B SFT+평가: 6시간)
|
||||
|
||||
---
|
||||
|
||||
## 📊 전체 타임라인 한눈에 보기
|
||||
|
||||
```
|
||||
Phase 0 ██░░░░░░░░░░░░░░░░░░░░░░ 30분 데이터/코드 준비
|
||||
Phase 1 ████░░░░░░░░░░░░░░░░░░░░ 40분 1B SFT 재학습
|
||||
Phase 2 ██████░░░░░░░░░░░░░░░░░░ 2시간 1B 평가
|
||||
────── 여기서 판단 ──────
|
||||
Phase 3A ████████░░░░░░░░░░░░░░░░ 3-5시간 (조건부) 1B 추가 개선
|
||||
Phase 3B ████████████████████████ 26시간 3B 사전학습
|
||||
Phase 4 ████░░░░░░░░░░░░░░░░░░░░ 2시간 3B SFT
|
||||
Phase 5 ██████░░░░░░░░░░░░░░░░░░ 4시간 평가 & 배포
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: 재학습 직전 준비 (오늘, ~30분)
|
||||
|
||||
### 체크리스트
|
||||
|
||||
#### ☐ 0-1. 데이터 재생성 (~20분)
|
||||
```bash
|
||||
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
|
||||
|
||||
# prepare_sft_data.py 재실행 (강화 필터 + 수정된 가중치)
|
||||
python data/prepare_sft_data.py \
|
||||
--output_dir data/sft_v2/ \
|
||||
--val_split 0.1
|
||||
```
|
||||
|
||||
**확인 사항**:
|
||||
- 필터링 후 **120K-135K 샘플** 남아야 함 (기존 159K에서 저품질 제거)
|
||||
- `</s>` 리터럴 113건, Q/A 마커 ~550건, 자체반복 57건 제거 확인
|
||||
- OpenOrca 가중치: 5.0 → 2.0으로 감소 확인
|
||||
- Val split: ~12-13K 샘플 (10%)
|
||||
- 짧은 output (<80자) 제거 확인
|
||||
|
||||
```bash
|
||||
# 결과 확인
|
||||
wc -l data/sft_v2/train.jsonl data/sft_v2/val.jsonl
|
||||
# 예상: train ~108K-120K, val ~12K-13K
|
||||
```
|
||||
|
||||
**완료 기준**: train 100K+ 샘플, val 10K+ 샘플. 제거된 샘플 spot check 시 실제 저품질.
|
||||
|
||||
#### ☐ 0-2. sft_dataset.py 수정 확인 (~5분)
|
||||
|
||||
이미 수정된 항목 확인:
|
||||
|
||||
| 수정 사항 | 파일 | 확인 |
|
||||
|-----------|------|------|
|
||||
| Dynamic padding 실제 작동 | `data/sft_dataset.py` `__getitem__` | ☐ 패딩 없이 실제 길이 텐서 반환 |
|
||||
| EOS 보존 | `data/sft_dataset.py` L130-134 | ☐ `response_ids[:allowed-1] + [eos_id]` |
|
||||
| Collate fn | `data/sft_dataset.py` `dynamic_collate_fn` | ☐ 배치별 가변 패딩 |
|
||||
|
||||
```bash
|
||||
# 핵심 코드 확인
|
||||
grep -n "allowed_response" data/sft_dataset.py
|
||||
grep -n "eos_token_id" data/sft_dataset.py
|
||||
grep -n "torch.full" data/sft_dataset.py # 4096 고정 패딩 없어야 함
|
||||
```
|
||||
|
||||
#### ☐ 0-3. launch_sft.sh 수정 (~5분)
|
||||
|
||||
```bash
|
||||
# 변경할 값들:
|
||||
# RUN_NAME=korean_1b_sft_v2
|
||||
# SFT_DATA=data/sft_v2/train.jsonl
|
||||
# VAL_DATA=data/sft_v2/val.jsonl
|
||||
# MAX_STEPS=10000 (3-4 epoch, 기존 5000에서 증가)
|
||||
# WARMUP_STEPS=300 (3%)
|
||||
|
||||
cp scripts/launch_sft.sh scripts/launch_sft_v2.sh
|
||||
# 편집 후 diff 확인
|
||||
```
|
||||
|
||||
#### ☐ 0-4. Sanity Check (~5분)
|
||||
|
||||
```bash
|
||||
# 100 steps만 빠르게 돌려서 파이프라인 정상 확인
|
||||
bash scripts/launch_sft_v2.sh --max_steps 100
|
||||
|
||||
# 확인:
|
||||
# - Loss가 2.0-2.5 범위에서 시작하는가? ✅
|
||||
# - 배치 내 시퀀스 길이가 가변적인가? (로그에서 확인) ✅
|
||||
# - Val loss가 출력되는가? ✅
|
||||
# - OOM 없는가? ✅
|
||||
```
|
||||
|
||||
**완료 기준**: 100 steps 에러 없이 완료, loss 합리적 범위, val loss 출력 확인.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: 1B SFT 재학습 (오늘, ~40분)
|
||||
|
||||
### 실행 명령어
|
||||
|
||||
```bash
|
||||
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
|
||||
|
||||
RUN_NAME=korean_1b_sft_v2 \
|
||||
BASE_CHECKPOINT=checkpoints/korean_1b_fp8_run1/checkpoint-0034000 \
|
||||
SFT_DATA=data/sft_v2/train.jsonl \
|
||||
VAL_DATA=data/sft_v2/val.jsonl \
|
||||
MAX_STEPS=10000 \
|
||||
WARMUP_STEPS=300 \
|
||||
LR=2.0e-5 \
|
||||
bash scripts/launch_sft.sh
|
||||
```
|
||||
|
||||
### 모니터링
|
||||
|
||||
**실시간 로그**:
|
||||
```bash
|
||||
tail -f checkpoints/korean_1b_sft_v2/train.log
|
||||
```
|
||||
|
||||
**TensorBoard**:
|
||||
```bash
|
||||
tensorboard --logdir checkpoints/korean_1b_sft_v2/tensorboard --port 6007
|
||||
```
|
||||
|
||||
**핵심 수치**:
|
||||
|
||||
| 수치 | 정상 범위 | 경고 | 즉시 중단 |
|
||||
|------|----------|------|----------|
|
||||
| Train Loss | 시작 2.0-2.5, 최종 <1.90 | >2.5 at step 500+ | >3.0 (발산) |
|
||||
| Val Loss | Train의 1.0-1.1배 | Train의 1.2배 | Train 대비 계속 상승 (과적합) |
|
||||
| GNorm | 0.8-1.5 | >2.0 | >5.0 (gradient 폭발) |
|
||||
| 학습 속도 | 기존 대비 2x+ (dynamic padding 효과) | 기존과 비슷 | 기존보다 느림 |
|
||||
|
||||
**체크포인트 관찰**:
|
||||
- Step 500: 파이프라인 안정성 확인
|
||||
- Step 2500: 중간 지점, loss 추세 확인
|
||||
- Step 5000: 기존 학습과 비교 (loss < 1.97이어야 함)
|
||||
- Step 7500: 수렴 여부 확인
|
||||
- Step 10000: 최종
|
||||
|
||||
### 성공 기준
|
||||
|
||||
| 지표 | 목표 | 실패 기준 |
|
||||
|------|------|----------|
|
||||
| Final Train Loss | < 1.90 | > 2.00 |
|
||||
| Final Val Loss | < 2.00 | Train 대비 1.2배 초과 |
|
||||
| Val Loss 추세 | 하강 or 안정 | 3연속 상승 (과적합) |
|
||||
| 학습 시간 | ~40-60분 | >2시간 (dynamic padding 미작동) |
|
||||
|
||||
### 실패 시 대응
|
||||
|
||||
| 상황 | 원인 추정 | 대응 |
|
||||
|------|----------|------|
|
||||
| Loss 발산 (>3.0) | LR 과다 or 데이터 버그 | LR=1e-5로 재시도 |
|
||||
| OOM | 배치 크기 과다 | BATCH_SIZE=2로 감소 |
|
||||
| Loss 정체 (step 2000+ 변화 없음) | LR 부족 or 데이터 문제 | 데이터 점검, LR=3e-5 시도 |
|
||||
| Val Loss 발산 (과적합) | Epoch 과다 | Early stop at best val checkpoint |
|
||||
| 학습 속도 기존과 같음 | Dynamic padding 미작동 | sft_dataset.py 재점검 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: 1B SFT 평가 (~2시간)
|
||||
|
||||
### 평가 순서
|
||||
|
||||
#### 2-1. 반복률 측정 (30분)
|
||||
|
||||
```bash
|
||||
# 올바른 포맷(<|user|>/<|assistant|>)으로 생성 테스트
|
||||
python eval/test_generation_params.py \
|
||||
--checkpoint checkpoints/korean_1b_sft_v2/checkpoint-0010000
|
||||
|
||||
# 다양한 rep_penalty 테스트
|
||||
# rep_penalty=1.0 (없음): 목표 <10%
|
||||
# rep_penalty=1.1: 목표 <3%
|
||||
# rep_penalty=1.2: 목표 <1%
|
||||
```
|
||||
|
||||
#### 2-2. 생성 품질 주관 평가 (30분)
|
||||
|
||||
```bash
|
||||
python eval/generate.py \
|
||||
--checkpoint checkpoints/korean_1b_sft_v2/checkpoint-0010000 \
|
||||
--prompts_file eval/test_prompts.txt \
|
||||
--temperature 0.8 --top_p 0.9
|
||||
```
|
||||
|
||||
**체크**: 한국어 자연스러움, instruction following, EOS 정상 종료
|
||||
|
||||
#### 2-3. 공식 벤치마크 (1시간)
|
||||
|
||||
```bash
|
||||
# ko_ifeval
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_1b_sft_v2/checkpoint-0010000,dtype=bfloat16 \
|
||||
--tasks ko_ifeval \
|
||||
--device cuda:0 \
|
||||
--output_path eval/results/sft_v2_ko_ifeval.json
|
||||
|
||||
# ko_winogrande (선택)
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_1b_sft_v2/checkpoint-0010000,dtype=bfloat16 \
|
||||
--tasks ko_winogrande \
|
||||
--device cuda:0 \
|
||||
--output_path eval/results/sft_v2_ko_winogrande.json
|
||||
```
|
||||
|
||||
### 판단 기준 & 분기
|
||||
|
||||
```
|
||||
[Phase 2 평가 결과]
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
✅ PASS ⚠️ PARTIAL ❌ FAIL
|
||||
반복률<5% 반복률 5-15% 반복률>15%
|
||||
ko_ifeval>25% ko_ifeval 15-25% ko_ifeval<15%
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Phase 3B Phase 3A 원인 분석
|
||||
(3B 전환) (추가 개선) (데이터/코드 재검토)
|
||||
```
|
||||
|
||||
**상세 기준**:
|
||||
|
||||
| 지표 | ✅ Pass | ⚠️ 추가 조정 | ❌ 재학습 |
|
||||
|------|---------|-------------|----------|
|
||||
| 반복률 (rep_penalty 없이) | <10% | 10-20% | >20% |
|
||||
| 반복률 (rep_penalty=1.1) | <5% | 5-15% | >15% |
|
||||
| ko_ifeval | >25% | 15-25% | <15% |
|
||||
| EOS 정상 종료율 | >85% | 60-85% | <60% |
|
||||
|
||||
---
|
||||
|
||||
## Phase 3A: 1B 추가 개선 (조건부, ~3-5시간)
|
||||
|
||||
> **Phase 2 결과가 ⚠️ PARTIAL일 때만 진입**
|
||||
|
||||
### 옵션 A: ORPO 학습 (~3시간)
|
||||
|
||||
#### Preference Data 준비 (1시간)
|
||||
```bash
|
||||
# 한국어 preference 데이터 다운로드
|
||||
python -c "
|
||||
from datasets import load_dataset
|
||||
# 옵션 1: ko_Ultrafeedback (60K, 일반 도메인)
|
||||
ds = load_dataset('maywell/ko_Ultrafeedback')
|
||||
# 옵션 2: 자체 생성 (현재 모델로 rejected 생성)
|
||||
"
|
||||
```
|
||||
|
||||
**자체 생성 방법**:
|
||||
1. 현재 SFT 모델로 동일 프롬프트에 여러 번 생성
|
||||
2. 반복/저품질 출력 → rejected
|
||||
3. 깨끗한 데이터의 정답 → chosen
|
||||
4. ~10K-20K 쌍 생성
|
||||
|
||||
#### ORPO 학습 (1.5시간)
|
||||
```python
|
||||
from trl import ORPOConfig, ORPOTrainer
|
||||
|
||||
config = ORPOConfig(
|
||||
learning_rate=5e-7,
|
||||
num_train_epochs=1,
|
||||
per_device_train_batch_size=4,
|
||||
gradient_accumulation_steps=2,
|
||||
beta=0.1, # ORPO coefficient
|
||||
)
|
||||
trainer = ORPOTrainer(model, config, train_dataset=preference_data)
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
#### 평가 (30분)
|
||||
- 반복률 재측정: 목표 <5% (rep_penalty=1.1)
|
||||
- ko_ifeval 재측정: 목표 >20%
|
||||
|
||||
### 옵션 B: 추가 SFT (데이터 보강, ~5시간)
|
||||
|
||||
#### 추가 데이터 수집 (2시간)
|
||||
```python
|
||||
from datasets import load_dataset
|
||||
|
||||
# 고품질 한국어 데이터 추가
|
||||
datasets = {
|
||||
"hPark/orca-ko": 200_000, # 고품질 합성
|
||||
"nayohan/llama3-instruct-ko-dataset": 58_000, # Llama3 한국어
|
||||
"FreedomIntelligence/evol-instruct-korean": 70_000, # GPT-4 생성
|
||||
}
|
||||
# 기존 120K + 추가 ~300K → 필터 후 ~350K
|
||||
```
|
||||
|
||||
#### 재학습 (2시간)
|
||||
```bash
|
||||
# 증가된 데이터로 재학습
|
||||
RUN_NAME=korean_1b_sft_v3 \
|
||||
SFT_DATA=data/sft_v3/train.jsonl \
|
||||
MAX_STEPS=15000 \
|
||||
bash scripts/launch_sft.sh
|
||||
```
|
||||
|
||||
### Phase 3A 성공 기준
|
||||
|
||||
| 지표 | 목표 |
|
||||
|------|------|
|
||||
| 반복률 (rep_penalty=1.1) | <5% |
|
||||
| ko_ifeval | >20% |
|
||||
|
||||
**실패 시**: 1B 한계 인정, Phase 3B (3B 전환)로 바로 이동.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3B: 3B 사전학습 (Phase 2 통과 후, ~26시간)
|
||||
|
||||
### 3B 모델 아키텍처
|
||||
|
||||
| 파라미터 | 1B (현재) | 3B (목표) | 비고 |
|
||||
|---------|----------|----------|------|
|
||||
| d_model | 2048 | 2560 | ~1.25x |
|
||||
| n_layers | 24 | 32 | ~1.33x |
|
||||
| n_heads | 16 | 32 | 2x |
|
||||
| n_kv_heads (GQA) | 4 | 8 | 2x |
|
||||
| d_ffn | 5472 | 6912 | ~1.26x |
|
||||
| vocab_size | 64000 | 64000 | 동일 |
|
||||
| max_seq_len | 4096 | 4096 | 동일 |
|
||||
| **총 파라미터** | **1.19B** | **~3.0B** | ~2.5x |
|
||||
|
||||
### 설정 파일 작성
|
||||
|
||||
```bash
|
||||
# configs/korean_3b_fp8.yaml 작성
|
||||
cat > configs/korean_3b_fp8.yaml << 'EOF'
|
||||
model:
|
||||
d_model: 2560
|
||||
n_layers: 32
|
||||
n_heads: 32
|
||||
n_kv_heads: 8
|
||||
d_ffn: 6912
|
||||
vocab_size: 64000
|
||||
max_seq_len: 4096
|
||||
rope_theta: 500000
|
||||
|
||||
training:
|
||||
lr: 3.0e-4
|
||||
min_lr: 3.0e-5
|
||||
warmup_steps: 2000
|
||||
max_steps: 100000
|
||||
batch_size: 4
|
||||
grad_accum: 4
|
||||
weight_decay: 0.1
|
||||
use_fp8: true
|
||||
|
||||
data:
|
||||
sources:
|
||||
- cc100_ko
|
||||
- culturax_ko
|
||||
- existing_pretrain
|
||||
EOF
|
||||
```
|
||||
|
||||
### 사전학습 데이터
|
||||
|
||||
| 소스 | 토큰 수 | 상태 |
|
||||
|------|---------|------|
|
||||
| CulturaX ko | 24.8B | ✅ 보유 |
|
||||
| cc100 ko (재수집) | ~65-100B | ⚠️ 재수집 필요 (노이즈 필터링) |
|
||||
| 기존 pretrain 데이터 | ~8.9B | ✅ 보유 |
|
||||
| 추가 수집 (나무위키, 뉴스 등) | ~20-50B | 선택적 |
|
||||
| **합계** | **~120-180B** | Chinchilla 60B 최소 충족 |
|
||||
|
||||
**데이터 준비 명령어**:
|
||||
```bash
|
||||
# cc100 재수집 + 품질 필터링
|
||||
python scripts/download_cc100_ko.py --quality_filter --dedup
|
||||
# MinHash dedup + perplexity filter
|
||||
python scripts/quality_filter.py --input data/pretrain/ --max_ppl 1000
|
||||
```
|
||||
|
||||
### 학습 실행
|
||||
|
||||
```bash
|
||||
# 3B pretrain 시작 (8× B200, ~26시간)
|
||||
bash scripts/run_pretrain.sh --config configs/korean_3b_fp8.yaml
|
||||
|
||||
# 예상 처리 속도: ~1.6M tok/s (8× B200)
|
||||
# 150B tokens / 1.6M tok/s ≈ 26시간
|
||||
```
|
||||
|
||||
### 모니터링
|
||||
|
||||
```bash
|
||||
# 로그 확인
|
||||
tail -f checkpoints/korean_3b_fp8/train.log
|
||||
|
||||
# 중간 체크포인트에서 base 품질 확인 (step 10000마다)
|
||||
python eval/perplexity.py --checkpoint checkpoints/korean_3b_fp8/checkpoint-0010000
|
||||
```
|
||||
|
||||
**성공 기준**: PPL < 10 (한국어 텍스트), loss 지속 하강
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: 3B SFT (~2시간)
|
||||
|
||||
### 1B에서 배운 교훈 전부 적용
|
||||
|
||||
| 교훈 | 적용 |
|
||||
|------|------|
|
||||
| Dynamic padding 작동 확인 | ✅ sft_dataset.py 수정 완료, 그대로 사용 |
|
||||
| EOS 보존 | ✅ 동일 코드 |
|
||||
| Val split 필수 | ✅ 10% split |
|
||||
| 3-4 epoch | ✅ MAX_STEPS 계산하여 설정 |
|
||||
| OpenOrca 과다 가중치 방지 | ✅ 2.0x 이하 |
|
||||
| 데이터 품질 필터링 | ✅ Phase 0에서 생성한 클린 데이터 사용 |
|
||||
| 올바른 프롬프트 포맷 | ✅ `<\|user\|>/<\|assistant\|>` |
|
||||
|
||||
### 실행
|
||||
|
||||
```bash
|
||||
RUN_NAME=korean_3b_sft \
|
||||
BASE_CHECKPOINT=checkpoints/korean_3b_fp8/checkpoint-BEST \
|
||||
SFT_DATA=data/sft_v2/train.jsonl \
|
||||
VAL_DATA=data/sft_v2/val.jsonl \
|
||||
MAX_STEPS=10000 \
|
||||
LR=2.0e-5 \
|
||||
WARMUP_STEPS=300 \
|
||||
bash scripts/launch_sft.sh
|
||||
```
|
||||
|
||||
**예상 시간**: ~2시간 (3B는 1B 대비 ~2.5x 느림)
|
||||
|
||||
### 성공 기준
|
||||
|
||||
| 지표 | 목표 |
|
||||
|------|------|
|
||||
| Train Loss | < 1.85 |
|
||||
| Val Loss | Train의 1.1배 이내 |
|
||||
| 반복률 (rep_penalty 없이) | < 10% |
|
||||
| 반복률 (rep_penalty=1.1) | < 3% |
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: 평가 및 배포 (~4시간)
|
||||
|
||||
### 5-1. 전체 벤치마크 (~2시간)
|
||||
|
||||
```bash
|
||||
# ko_ifeval
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_3b_sft/checkpoint-BEST,dtype=bfloat16 \
|
||||
--tasks ko_ifeval --device cuda:0
|
||||
|
||||
# ko_winogrande
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_3b_sft/checkpoint-BEST,dtype=bfloat16 \
|
||||
--tasks ko_winogrande --device cuda:0
|
||||
|
||||
# KoBEST (선택)
|
||||
lm_eval --model hf \
|
||||
--model_args pretrained=checkpoints/korean_3b_sft/checkpoint-BEST,dtype=bfloat16 \
|
||||
--tasks kobest_boolq,kobest_copa,kobest_wic,kobest_hellaswag,kobest_sentineg \
|
||||
--device cuda:0
|
||||
```
|
||||
|
||||
**3B 목표 수치**:
|
||||
|
||||
| 벤치마크 | 1B 예상 | 3B 목표 |
|
||||
|---------|---------|---------|
|
||||
| ko_ifeval | 20-30% | **35-45%** |
|
||||
| ko_winogrande | 53-58% | **60-68%** |
|
||||
| KoBEST (avg) | 55-60% | **65-75%** |
|
||||
| 반복률 | <5% | **<3%** |
|
||||
|
||||
### 5-2. HuggingFace Hub 업로드 (~1시간)
|
||||
|
||||
```bash
|
||||
# HF 포맷 변환
|
||||
python scripts/convert_to_hf.py \
|
||||
--checkpoint checkpoints/korean_3b_sft/checkpoint-BEST \
|
||||
--output_dir hf_models/korean-3b-instruct
|
||||
|
||||
# Model card 작성
|
||||
cat > hf_models/korean-3b-instruct/README.md << 'EOF'
|
||||
---
|
||||
language: ko
|
||||
license: apache-2.0
|
||||
tags:
|
||||
- korean
|
||||
- llm
|
||||
- instruction-tuning
|
||||
---
|
||||
# Korean 3B Instruct
|
||||
...벤치마크 결과, 사용법 등...
|
||||
EOF
|
||||
|
||||
# 업로드
|
||||
huggingface-cli upload ghong/korean-3b-instruct hf_models/korean-3b-instruct
|
||||
```
|
||||
|
||||
### 5-3. vLLM 서빙 설정 (~1시간)
|
||||
|
||||
```bash
|
||||
# vLLM 서버 시작
|
||||
python -m vllm.entrypoints.openai.api_server \
|
||||
--model hf_models/korean-3b-instruct \
|
||||
--dtype bfloat16 \
|
||||
--tensor-parallel-size 1 \
|
||||
--max-model-len 4096 \
|
||||
--port 8000
|
||||
|
||||
# 테스트
|
||||
curl http://localhost:8000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "korean-3b-instruct",
|
||||
"messages": [{"role": "user", "content": "한국의 수도는?"}],
|
||||
"temperature": 0.7
|
||||
}'
|
||||
```
|
||||
|
||||
**FP8 서빙 (B200 최적)**:
|
||||
```bash
|
||||
python -m vllm.entrypoints.openai.api_server \
|
||||
--model hf_models/korean-3b-instruct \
|
||||
--quantization fp8 \
|
||||
--tensor-parallel-size 1 \
|
||||
--max-model-len 4096
|
||||
```
|
||||
|
||||
**GGUF 변환 (Ollama/로컬 배포)**:
|
||||
```bash
|
||||
bash scripts/convert_to_gguf.sh checkpoints/korean_3b_sft/checkpoint-BEST
|
||||
# Ollama Modelfile 작성 후
|
||||
ollama create korean-3b -f Modelfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase별 요약 테이블
|
||||
|
||||
| Phase | 소요 시간 | 필요한 것 | 성공 기준 | 실패 시 |
|
||||
|-------|----------|----------|----------|---------|
|
||||
| **0: 준비** | 30분 | prepare_sft_data.py, sft_dataset.py 수정 | 클린 데이터 120K+, sanity 100steps 통과 | 코드 디버그 |
|
||||
| **1: 1B SFT** | 40분 | 8×B200, 클린 데이터, 수정된 코드 | Loss<1.90, ValLoss 안정 | LR 조정 or 데이터 재점검 |
|
||||
| **2: 1B 평가** | 2시간 | lm-eval-harness, 평가 스크립트 | 반복률<5%, ko_ifeval>25% | Phase 3A |
|
||||
| **3A: 추가개선** | 3-5시간 | Preference 데이터, ORPO/추가 SFT | 반복률<5% 달성 | 1B 한계 인정→3B |
|
||||
| **3B: 3B PT** | 26시간 | 150B+ 토큰, configs/korean_3b_fp8.yaml | PPL<10, loss 하강 | 데이터 추가 or 아키텍처 조정 |
|
||||
| **4: 3B SFT** | 2시간 | Phase 0의 클린 데이터 재사용 | Loss<1.85, 반복률<3% | LR/epoch 조정 |
|
||||
| **5: 배포** | 4시간 | HF 계정, vLLM | ko_ifeval>35%, 서빙 정상 | 모델 개선 후 재배포 |
|
||||
|
||||
---
|
||||
|
||||
## 🔥 오늘 당장 시작할 첫 번째 명령어
|
||||
|
||||
```bash
|
||||
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
|
||||
python data/prepare_sft_data.py --output_dir data/sft_v2/ --val_split 0.1
|
||||
```
|
||||
|
||||
이 명령어 하나로 Phase 0의 가장 중요한 작업(클린 데이터 생성)이 시작된다.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 가장 중요한 판단 포인트
|
||||
|
||||
### 1차 판단: Phase 1 완료 후 (Step 10000)
|
||||
- **Val Loss가 Train Loss의 1.2배 이상?** → 과적합. Best checkpoint 사용.
|
||||
- **Train Loss > 2.0?** → 무언가 잘못됨. 코드/데이터 재점검.
|
||||
|
||||
### 2차 판단: Phase 2 평가 후 (가장 중요!)
|
||||
- **반복률 <5% AND ko_ifeval >25%?** → ✅ 3B 전환 (Phase 3B)
|
||||
- **반복률 5-15%?** → ⚠️ ORPO 시도 (Phase 3A)
|
||||
- **반복률 >15%?** → ❌ 원인 분석. 데이터/코드 재검토.
|
||||
|
||||
### 3차 판단: Phase 3B 중간 (3B pretrain step 50000)
|
||||
- **Loss 하강 멈춤?** → 데이터 품질 문제. 필터링 강화.
|
||||
- **PPL > 15?** → 데이터 부족. 추가 수집 필요.
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 리스크 매트릭스
|
||||
|
||||
| 리스크 | 확률 | 영향 | 예방/대응 |
|
||||
|--------|------|------|----------|
|
||||
| Dynamic padding 여전히 미작동 | 10% | 높음 (속도 3-8x 낭비) | Sanity check에서 배치 길이 확인 |
|
||||
| 데이터 필터링 과다 (100K 미만) | 15% | 중간 | 필터 기준 완화 (80자→50자) |
|
||||
| 1B 재학습 후에도 반복 >15% | 15% | 중간 | ORPO or 3B 전환 |
|
||||
| 3B pretrain 중 OOM | 10% | 높음 | batch_size 줄이기, gradient checkpointing |
|
||||
| cc100 재수집 시간 초과 | 20% | 낮음 | CulturaX만으로 시작 (24.8B) |
|
||||
| 디스크 공간 부족 | 5% | 높음 | 현재 19TB 가용, 충분 |
|
||||
|
||||
---
|
||||
|
||||
*"40분 아끼려고 기술 부채를 안고 가지 마라. 3시간 투자해서 깨끗한 기반을 만들어라."*
|
||||
|
||||
*이 문서는 각 Phase 완료 시 결과로 업데이트할 것.*
|
||||
447
source/eval/plan/exception_playbook.md
Normal file
447
source/eval/plan/exception_playbook.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# SFT 학습 예외 상황 플레이북
|
||||
|
||||
**프로젝트:** Korean 1B SFT 재학습
|
||||
**서버:** 8× B200 183GB, Driver 580.95.05, CUDA 13.1, PyTorch 2.10
|
||||
**작성일:** 2026-02-26
|
||||
**설정:** bs=4 × 8GPU × grad_accum=2 = effective batch 64, max_steps=10000, lr=2e-5, FP8
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 1: Loss가 0으로 떨어지는 경우
|
||||
|
||||
### 감지 기준
|
||||
- **즉각 경고:** loss < 0.01이 3 step 연속 발생
|
||||
- **주의:** loss < 0.1이 10 step 이상 지속
|
||||
- **정상 범위:** 1B SFT에서 수렴 시 loss ≈ 1.5~2.0. 0에 가까우면 100% 비정상
|
||||
|
||||
### 즉각 대응
|
||||
1. 학습 즉시 중단 (Ctrl+C 또는 `kill -SIGINT <PID>`)
|
||||
2. 가장 최근 정상 체크포인트 확인:
|
||||
```bash
|
||||
ls -lt checkpoints/korean_1b_sft/checkpoint-* | head -5
|
||||
```
|
||||
|
||||
### 원인별 진단 및 대응
|
||||
|
||||
#### 1-A. Labels Shift 버그 재발
|
||||
**확인 방법:**
|
||||
```python
|
||||
# 데이터에서 샘플 하나 로드 후 labels 검증
|
||||
from data.sft_dataset import SFTDataset
|
||||
from tokenizers import Tokenizer
|
||||
tok = Tokenizer.from_file("tokenizer/korean_sp/tokenizer.json")
|
||||
ds = SFTDataset("data/sft/train.jsonl", tok, max_seq_len=4096)
|
||||
ids, labels = ds[0]
|
||||
# labels에서 -1이 아닌 부분이 input_ids의 다음 토큰과 일치하는지 확인
|
||||
mask = labels != -1
|
||||
print(f"유효 labels 수: {mask.sum()}")
|
||||
print(f"첫 유효 label 위치: {mask.nonzero()[0].item() if mask.any() else 'NONE'}")
|
||||
# labels[i]는 input_ids[i+1]과 같아야 함 (autoregressive)
|
||||
# 만약 labels == input_ids 이면 shift 안 됨 → 버그
|
||||
```
|
||||
**수정:** `sft_dataset.py`에서 `labels = input_ids[1:]`, `input_ids = input_ids[:-1]` shift 확인
|
||||
|
||||
#### 1-B. 데이터 오염
|
||||
**확인 방법:**
|
||||
```python
|
||||
# 랜덤 배치에서 실제 학습 토큰 검사
|
||||
for batch in train_loader:
|
||||
ids, labels, mask = batch
|
||||
valid = (labels != -1)
|
||||
print(f"유효 토큰 비율: {valid.float().mean():.4f}")
|
||||
# 유효 토큰이 0이면 모든 labels가 -1 → loss=0
|
||||
if valid.sum() == 0:
|
||||
print("🔴 모든 labels가 ignore_index! 데이터 문제")
|
||||
break
|
||||
```
|
||||
**대응:** 데이터 재생성, `prepare_sft_data.py` 재실행
|
||||
|
||||
#### 1-C. Learning Rate 문제
|
||||
**확인:** loss가 갑자기 0이면 lr 문제보다는 labels 버그일 가능성이 훨씬 높음. 그래도 확인:
|
||||
```bash
|
||||
grep "lr " checkpoints/korean_1b_sft/train.log | tail -20
|
||||
# lr이 비정상적으로 높으면 (>1e-3) 수정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 2: Loss Spike (급등)
|
||||
|
||||
### 감지 기준
|
||||
- **Spike 정의:** 이전 log_interval 평균 대비 **3배 이상** 급등
|
||||
- **예:** 평균 loss 1.9에서 갑자기 5.7 이상
|
||||
- **GNorm 기준:** grad_norm > 10.0이면 주의, > 50.0이면 심각
|
||||
|
||||
### 원인별 대응
|
||||
|
||||
| 원인 | 진단 | 대응 |
|
||||
|------|------|------|
|
||||
| Bad batch (이상 데이터) | 해당 step의 배치 내용 확인 | 1~2회 spike 후 자연 복구되면 무시 |
|
||||
| LR 문제 | warmup 직후 spike → lr 너무 높음 | lr을 1e-5로 낮추고 재시작 |
|
||||
| GNorm 폭발 | gnorm > 50 | max_grad_norm을 0.5로 강화 |
|
||||
| FP8 수치 불안정 | FP8 관련 warning 확인 | `--use_fp8` 제거하고 BF16으로 전환 |
|
||||
|
||||
### 대응 절차
|
||||
1. **1회 spike:** 무시 (단발성 bad batch). 다음 log에서 복구 확인
|
||||
2. **연속 3회 spike:** 학습 중단
|
||||
3. **복구 방법:**
|
||||
```bash
|
||||
# 마지막 정상 체크포인트에서 재시작, lr 낮추기
|
||||
bash scripts/launch_sft.sh --resume checkpoints/korean_1b_sft/checkpoint-XXXX --lr 1e-5
|
||||
```
|
||||
|
||||
### 현재 코드의 보호 장치
|
||||
- ✅ `max_grad_norm=1.0` (gradient clipping 활성화)
|
||||
- ✅ Non-finite loss 감지 → RuntimeError 발생 (trainer.py `_step()`)
|
||||
- ❌ Loss spike 자동 감지/skip은 미구현 → `monitor_training.sh`로 보완
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 3: 과적합 (val_loss > train_loss 지속)
|
||||
|
||||
### 감지 기준
|
||||
- **주의:** val_loss - train_loss > 0.15 (상대갭 8% 이상)
|
||||
- **심각:** val_loss가 3회 연속 eval에서 상승 (train_loss는 하강 중)
|
||||
- **eval_interval:** 현재 250 steps → 매 250 step마다 val_loss 기록됨
|
||||
|
||||
### 현재 코드 상태
|
||||
- ✅ `val_loader` 지원 (sft.py에서 `--val_data` 인자 있음)
|
||||
- ✅ `_run_validation()` 구현됨 (trainer.py)
|
||||
- ✅ Best checkpoint 자동 저장 (`val_loss < self._best_val_loss`)
|
||||
- ❌ **Early stopping 미구현** — val_loss가 올라도 max_steps까지 학습 계속
|
||||
|
||||
### 대응
|
||||
|
||||
#### 즉시 가능한 조치
|
||||
1. **수동 early stop:** 모니터링 스크립트가 경고 → 수동 중단
|
||||
2. **Best checkpoint 사용:** `checkpoint-best` 디렉토리에 자동 저장됨
|
||||
```bash
|
||||
ls checkpoints/korean_1b_sft/checkpoint-best/
|
||||
```
|
||||
|
||||
#### 과적합 해소 방법 (재학습 시)
|
||||
| 방법 | 설정 변경 |
|
||||
|------|-----------|
|
||||
| LR 낮추기 | `--lr 1e-5` |
|
||||
| Weight decay 높이기 | `--weight_decay 0.05` |
|
||||
| 데이터 augmentation | NEFTune 이미 활성화 (noise_alpha=10.0) ✅ |
|
||||
| Steps 줄이기 | `--max_steps 7000` (과적합 시작 전 step에서 멈춤) |
|
||||
| Dropout | 모델 구조 수정 필요 (현재 코드에서 쉽지 않음) |
|
||||
|
||||
#### Early Stopping 추가 방법 (trainer.py 수정)
|
||||
```python
|
||||
# trainer.py의 train() 메서드에서 validation 후:
|
||||
if val_loss > self._best_val_loss:
|
||||
self._patience_counter += 1
|
||||
if self._patience_counter >= 5: # 5회 연속 개선 없으면 중단
|
||||
self._log("Early stopping triggered")
|
||||
break
|
||||
else:
|
||||
self._patience_counter = 0
|
||||
self._best_val_loss = val_loss
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 4: OOM (Out of Memory)
|
||||
|
||||
### 현재 메모리 추정
|
||||
|
||||
| 항목 | 추정 |
|
||||
|------|------|
|
||||
| 모델 파라미터 (1.19B, BF16) | ~2.4 GB |
|
||||
| 옵티마이저 상태 (AdamW, fp32) | ~9.5 GB |
|
||||
| Gradient (BF16) | ~2.4 GB |
|
||||
| Activation (bs=4, seq=4096, gradient checkpointing ON) | ~8-15 GB |
|
||||
| **Peak 총합 (per GPU)** | **~25-35 GB** |
|
||||
| **B200 여유** | **183 - 35 = ~148 GB 여유** |
|
||||
|
||||
→ 1B 모델에서 OOM 가능성 **극히 낮음**
|
||||
|
||||
### 만약 발생한다면
|
||||
1. **증상:** `torch.cuda.OutOfMemoryError` → trainer.py에서 이미 catch하여 상세 메시지 출력
|
||||
2. **즉시 대응:**
|
||||
```bash
|
||||
# batch_size 줄이기 (4→2), grad_accum 늘리기 (2→4) → effective batch 동일
|
||||
bash scripts/launch_sft.sh --batch_size 2 --grad_accum 4 --resume <last_ckpt>
|
||||
```
|
||||
3. **Gradient checkpointing:**
|
||||
- ✅ **이미 활성화됨** (sft.py에서 `model.gradient_checkpointing_enable()`)
|
||||
4. **추가 조치:**
|
||||
```bash
|
||||
# 메모리 fragmentation 방지
|
||||
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
||||
```
|
||||
|
||||
### 메모리 모니터링
|
||||
```bash
|
||||
watch -n 5 nvidia-smi # 실시간 확인
|
||||
# 또는 monitor_training.sh 사용 (아래 참조)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 5: GPU Hang / NCCL 통신 장애
|
||||
|
||||
### 감지 방법
|
||||
- **증상:** 학습 로그가 멈춤 (새 step이 N분 이상 안 나옴)
|
||||
- **NCCL timeout:** 기본 30분 후 에러 발생
|
||||
- `nvidia-smi`에서 특정 GPU utilization 0%
|
||||
|
||||
### 진단
|
||||
```bash
|
||||
# 1. GPU 상태 확인
|
||||
nvidia-smi
|
||||
|
||||
# 2. NCCL 디버그 활성화하여 재시작
|
||||
export NCCL_DEBUG=INFO
|
||||
export NCCL_DEBUG_SUBSYS=ALL
|
||||
|
||||
# 3. 프로세스 상태 확인
|
||||
ps aux | grep torchrun
|
||||
```
|
||||
|
||||
### 복구 방법
|
||||
```bash
|
||||
# 1. 기존 프로세스 정리
|
||||
pkill -f torchrun
|
||||
sleep 5
|
||||
|
||||
# 2. 가장 최근 체크포인트 자동 감지
|
||||
LATEST_CKPT=$(ls -d checkpoints/korean_1b_sft/checkpoint-* 2>/dev/null \
|
||||
| grep -v best | sort -t- -k2 -n | tail -1)
|
||||
echo "Latest checkpoint: ${LATEST_CKPT}"
|
||||
|
||||
# 3. 재시작
|
||||
bash scripts/launch_sft.sh --resume "${LATEST_CKPT}"
|
||||
```
|
||||
|
||||
### 최근 체크포인트 자동 감지 스크립트
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# find_latest_checkpoint.sh
|
||||
CKPT_DIR="${1:-checkpoints/korean_1b_sft}"
|
||||
LATEST=$(ls -d "${CKPT_DIR}"/checkpoint-[0-9]* 2>/dev/null \
|
||||
| sort -t- -k2 -n | tail -1)
|
||||
if [[ -z "$LATEST" ]]; then
|
||||
echo "No checkpoint found in ${CKPT_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$LATEST"
|
||||
```
|
||||
|
||||
### 예방
|
||||
- `save_interval=500` (현재 설정) → 최대 500 step 손실
|
||||
- NCCL timeout 조정: `export NCCL_TIMEOUT=1800` (30분 → 필요 시 줄이기)
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 6: 학습 완료 후 반복률 >15%
|
||||
|
||||
### 판단 기준
|
||||
|
||||
| 반복률 | 판단 | 대응 |
|
||||
|--------|------|------|
|
||||
| <5% (rep_penalty 없이) | ✅ 성공 | 배포 가능 |
|
||||
| 5-10% | 🟡 OK | rep_penalty=1.1로 배포 |
|
||||
| 10-20% | 🟠 경계 | 아래 파라미터 조정 시도 |
|
||||
| >20% | 🔴 실패 | 재학습 필요 |
|
||||
|
||||
### 파라미터 조정으로 해결 시도 (재학습 없이)
|
||||
|
||||
```python
|
||||
# 추론 시 적용
|
||||
generate_kwargs = {
|
||||
"repetition_penalty": 1.1, # 1.05~1.2 범위 탐색
|
||||
"no_repeat_ngram_size": 3, # 3-gram 반복 차단
|
||||
"temperature": 0.7, # 약간 낮추면 반복 감소
|
||||
"top_p": 0.9,
|
||||
}
|
||||
```
|
||||
|
||||
### 재학습이 필요한 경우
|
||||
- rep_penalty=1.2 + no_repeat_3gram에서도 >10%
|
||||
- 원인 분석:
|
||||
1. **데이터 내 반복 패턴:** `data_quality_audit.py`로 재확인
|
||||
2. **Epoch 과다:** 5+ epoch은 반복 패턴 암기 유발 → 3-4 epoch이 적정
|
||||
3. **EOS 학습 부족:** truncation 시 EOS 손실 여부 확인
|
||||
|
||||
### 고급 대응 (추가 학습 방법)
|
||||
| 방법 | 설명 | 소요 |
|
||||
|------|------|------|
|
||||
| ORPO | Preference optimization, 반복 패턴 직접 penalize | +3-6시간 |
|
||||
| DPO | Chosen(비반복) vs Rejected(반복) 쌍 필요 | +4-8시간 |
|
||||
| rep_penalty fine-tuning | 추론 시 penalty 결과를 reward로 RL | 복잡 |
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 7: ko_ifeval 기대치 미달 (<15%)
|
||||
|
||||
### 원인 분석 방법
|
||||
|
||||
#### Step 1: 모델 출력 직접 확인
|
||||
```bash
|
||||
# ko_ifeval 실패 샘플 분석
|
||||
python -c "
|
||||
# lm_eval 결과에서 실패 케이스 추출
|
||||
# 지시문 이해 부족 vs 포맷 오류 vs 한국어 능력 부족 구분
|
||||
"
|
||||
```
|
||||
|
||||
#### Step 2: 카테고리별 분석
|
||||
| 실패 유형 | 의미 | 대응 |
|
||||
|-----------|------|------|
|
||||
| 지시 무시 (wrong format) | instruction following 약함 | SFT 데이터에 format-constrained 샘플 추가 |
|
||||
| 한국어 이해 실패 | 한국어 능력 부족 | 한국어 비율 높이기 (현재 ~70%) |
|
||||
| 추론 오류 | 1B 모델 한계 | 모델 크기 한계 → 3B 전환 |
|
||||
|
||||
#### Step 3: 모델 한계 vs 데이터 문제 구분
|
||||
```
|
||||
1B 모델 ko_ifeval 현실적 범위: 15-30%
|
||||
- <15%: 데이터/학습 문제 가능성 높음
|
||||
- 15-25%: 정상 범위, 데이터로 개선 여지 있음
|
||||
- 25-30%: 1B 한계에 근접, 3B 전환 필요
|
||||
- >30%: 1B에서 달성하기 어려움
|
||||
```
|
||||
|
||||
### 데이터 추가 수집 방향
|
||||
1. **Korean instruction-following 데이터:** KoAlpaca, KULLM 등에서 format-constrained 샘플
|
||||
2. **Multi-turn 한국어 대화:** 지시 따르기 능력 강화
|
||||
3. **ko_ifeval과 유사한 포맷 데이터:** "~형식으로 답하시오" 유형
|
||||
|
||||
---
|
||||
|
||||
## 시나리오 8: 디스크 공간 부족
|
||||
|
||||
### 현재 상태
|
||||
```
|
||||
/PROJECT: 3.5TB 총, 1.4TB 사용, 2.2TB 가용 (39% 사용)
|
||||
```
|
||||
|
||||
### 체크포인트 크기 추정
|
||||
| 항목 | 크기 |
|
||||
|------|------|
|
||||
| model.pt (1.19B BF16) | ~2.4 GB |
|
||||
| optimizer.pt (AdamW states) | ~9.5 GB |
|
||||
| scheduler + meta | ~1 MB |
|
||||
| **체크포인트 1개** | **~12 GB** |
|
||||
| 10,000 steps / 500 save = 20개 | **~240 GB** |
|
||||
| + best checkpoint | +12 GB |
|
||||
| + tensorboard logs | ~100 MB |
|
||||
| **총 예상** | **~252 GB** |
|
||||
|
||||
→ 2.2TB 가용 대비 충분하지만, 여러 실험 시 누적 주의
|
||||
|
||||
### 체크포인트 관리 전략
|
||||
|
||||
#### 저장 주기 최적화
|
||||
- **현재:** 500 step마다 (추천 유지)
|
||||
- 디스크 부족 시: 1000 step으로 변경 → 120 GB로 절반 감소
|
||||
- `train_config.save_interval = 1000`
|
||||
|
||||
#### 오래된 체크포인트 자동 삭제
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# cleanup_checkpoints.sh — 최신 N개만 유지, best는 항상 보존
|
||||
CKPT_DIR="${1:-checkpoints/korean_1b_sft}"
|
||||
KEEP="${2:-5}" # 최신 5개 유지
|
||||
|
||||
CKPTS=$(ls -d "${CKPT_DIR}"/checkpoint-[0-9]* 2>/dev/null | sort -t- -k2 -n)
|
||||
TOTAL=$(echo "$CKPTS" | wc -l)
|
||||
DELETE=$((TOTAL - KEEP))
|
||||
|
||||
if [[ $DELETE -gt 0 ]]; then
|
||||
echo "$CKPTS" | head -n "$DELETE" | while read ckpt; do
|
||||
echo "Removing: $ckpt"
|
||||
rm -rf "$ckpt"
|
||||
done
|
||||
echo "Kept latest $KEEP checkpoints + checkpoint-best"
|
||||
else
|
||||
echo "Only $TOTAL checkpoints, nothing to delete (keep=$KEEP)"
|
||||
fi
|
||||
```
|
||||
|
||||
### 디스크 모니터링
|
||||
```bash
|
||||
# 학습 중 주기적 확인
|
||||
df -h /PROJECT | awk 'NR==2 {if ($5+0 > 80) print "🔴 DISK >80%: "$5}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 학습 재시작 가이드
|
||||
|
||||
### 현재 코드의 Resume 지원
|
||||
|
||||
✅ **완전 지원됨:**
|
||||
- `sft.py`에 `--resume` 인자 있음
|
||||
- `load_checkpoint()`으로 model, optimizer, scheduler 상태 모두 복원
|
||||
- `start_step` 반환 → 이어서 학습
|
||||
|
||||
### 재시작 명령어
|
||||
```bash
|
||||
# 방법 1: 최신 체크포인트에서 자동 재시작
|
||||
LATEST=$(ls -d checkpoints/korean_1b_sft/checkpoint-[0-9]* 2>/dev/null \
|
||||
| sort -t- -k2 -n | tail -1)
|
||||
bash scripts/launch_sft.sh --resume "${LATEST}"
|
||||
|
||||
# 방법 2: 특정 체크포인트 지정
|
||||
bash scripts/launch_sft.sh --resume checkpoints/korean_1b_sft/checkpoint-0003000
|
||||
|
||||
# 방법 3: LR 변경하며 재시작 (과적합/spike 대응)
|
||||
bash scripts/launch_sft.sh --resume "${LATEST}" --lr 1e-5
|
||||
```
|
||||
|
||||
### 주의사항
|
||||
- **cosine schedule:** resume 시 scheduler가 중간 step에서 복원됨 → LR이 올바른 위치에서 재개
|
||||
- **max_steps 변경 시:** 원래 5000 step 기준 schedule인데 10000으로 변경하면 LR curve가 달라짐 → 처음부터 재학습 권장
|
||||
- **DDP seed:** resume 시 동일 seed 사용해야 데이터 순서 재현 (현재 코드에서 자동 처리)
|
||||
|
||||
---
|
||||
|
||||
## 모니터링 자동화
|
||||
|
||||
별도 스크립트: `scripts/monitor_training.sh` 참조
|
||||
|
||||
### 감시 항목 요약
|
||||
|
||||
| 항목 | 임계값 | 의미 |
|
||||
|------|--------|------|
|
||||
| loss = 0.0000 (3 step 연속) | 🔴 Critical | Labels 버그 |
|
||||
| loss spike (3× 평균) | 🟠 Warning | Bad batch / LR |
|
||||
| gnorm > 10.0 | 🟠 Warning | 불안정 |
|
||||
| gnorm > 50.0 | 🔴 Critical | 발산 직전 |
|
||||
| GPU util < 50% | 🟡 Info | 병목 (data loading?) |
|
||||
| 로그 5분 이상 멈춤 | 🔴 Critical | Hang / NCCL 장애 |
|
||||
| 디스크 사용 > 80% | 🟠 Warning | 체크포인트 정리 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 위험도 순위 (높음 → 낮음)
|
||||
|
||||
| 순위 | 시나리오 | 위험도 | 예방 |
|
||||
|------|----------|--------|------|
|
||||
| 1 | **Loss → 0 (Labels 버그)** | 🔴🔴🔴 | 학습 전 labels shift 검증 스크립트 실행 |
|
||||
| 2 | **GPU Hang (NCCL)** | 🔴🔴 | save_interval=500, NCCL 환경변수 설정 |
|
||||
| 3 | **과적합** | 🔴 | val_data 필수, 모니터링 |
|
||||
| 4 | **반복률 >15%** | 🟠🟠 | 깨끗한 데이터, 적정 epoch |
|
||||
| 5 | **Loss Spike** | 🟠 | grad_clip=1.0, 이미 설정됨 |
|
||||
| 6 | **ko_ifeval 미달** | 🟠 | 1B 한계 인지, 데이터 다양성 |
|
||||
| 7 | **디스크 부족** | 🟡 | 2.2TB 여유, 자동 정리 |
|
||||
| 8 | **OOM** | 🟢 | 183GB에 1B 모델, 거의 불가능 |
|
||||
|
||||
---
|
||||
|
||||
## 학습 전 체크리스트
|
||||
|
||||
```
|
||||
□ 데이터 필터링 완료 (data_quality_audit.py)
|
||||
□ Val split 생성 (90/10)
|
||||
□ Labels shift 검증 (위 코드 스니펫 실행)
|
||||
□ sft_dataset.py 수정 확인 (dynamic padding, EOS 보존)
|
||||
□ launch_sft.sh 설정 확인 (max_steps, val_data, lr)
|
||||
□ 디스크 공간 확인 (df -h /PROJECT)
|
||||
□ GPU 상태 확인 (nvidia-smi)
|
||||
□ monitor_training.sh 백그라운드 실행
|
||||
□ tensorboard 실행: tensorboard --logdir checkpoints/korean_1b_sft/tensorboard
|
||||
```
|
||||
533
source/eval/plan/improvement_options.md
Normal file
533
source/eval/plan/improvement_options.md
Normal file
@@ -0,0 +1,533 @@
|
||||
# SFT 개선 방안 심층 조사
|
||||
|
||||
> 프로젝트: 1B Korean LLM SFT (188k 샘플, 8×B200, FP8)
|
||||
> 현재 구현: NEFTune, dynamic padding, gradient checkpointing, cosine LR, BF16+FP8
|
||||
> 작성일: 2026-02-26
|
||||
|
||||
---
|
||||
|
||||
## 1. Curriculum Learning (교육과정 학습)
|
||||
|
||||
### 개념
|
||||
쉬운 샘플에서 어려운 샘플 순서로 학습하여 수렴 속도와 최종 성능 향상.
|
||||
|
||||
### 구현 방법
|
||||
|
||||
**방법 A: Perplexity 기반 정렬 (권장)**
|
||||
```python
|
||||
# scripts/compute_difficulty.py
|
||||
import torch, json
|
||||
from pathlib import Path
|
||||
from tokenizers import Tokenizer
|
||||
from model import LLM
|
||||
|
||||
def compute_sample_perplexity(model, tokenizer, data_path, output_path, device="cuda:0"):
|
||||
"""현재 pretrain 모델로 각 샘플의 output perplexity 계산"""
|
||||
model.eval()
|
||||
results = []
|
||||
|
||||
with open(data_path) as f:
|
||||
samples = [json.loads(line) for line in f]
|
||||
|
||||
with torch.no_grad():
|
||||
for i, sample in enumerate(samples):
|
||||
# conversation에서 assistant turn만 추출
|
||||
messages = sample["messages"]
|
||||
# 전체 시퀀스 토크나이즈
|
||||
full_text = tokenizer.encode(
|
||||
"".join(m["content"] for m in messages)
|
||||
)
|
||||
input_ids = torch.tensor([full_text.ids[:4096]], device=device)
|
||||
|
||||
logits = model(input_ids)
|
||||
# response 토큰에 대한 CE loss = perplexity proxy
|
||||
shift_logits = logits[:, :-1, :]
|
||||
shift_labels = input_ids[:, 1:]
|
||||
loss = torch.nn.functional.cross_entropy(
|
||||
shift_logits.reshape(-1, shift_logits.size(-1)),
|
||||
shift_labels.reshape(-1),
|
||||
reduction='mean'
|
||||
)
|
||||
ppl = loss.exp().item()
|
||||
results.append({"idx": i, "ppl": ppl})
|
||||
|
||||
if i % 1000 == 0:
|
||||
print(f" {i}/{len(samples)} done")
|
||||
|
||||
# ppl 오름차순 정렬 = 쉬운 것부터
|
||||
results.sort(key=lambda x: x["ppl"])
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(results, f)
|
||||
return results
|
||||
```
|
||||
|
||||
**방법 B: 길이 기반 (가장 간단)**
|
||||
- 짧은 응답 → 긴 응답 순서로 정렬
|
||||
- SFTDataset에서 `__getitem__` 시 정렬된 인덱스 사용
|
||||
|
||||
**방법 C: IFD Score**
|
||||
- Cherry LLM 논문 (2024): `IFD = PPL(output|instruction) / PPL(output)`
|
||||
- 높은 IFD = instruction이 output 생성을 잘 유도하지 못함 = 어려움
|
||||
|
||||
### 실제 효과
|
||||
- **Curriculum Learning for LLMs (Xu et al., 2024)**: SFT에서 MT-Bench +0.3~0.5점
|
||||
- **효과 제한적 의견**: Bengio et al.의 원 연구 이후 SFT에서의 효과는 mixed results
|
||||
- **예상**: ko_ifeval +1~2%, 반복률 변화 미미
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | ko_ifeval +1~2% |
|
||||
| 구현 복잡도 | 2/5 |
|
||||
| 소요 시간 | PPL 계산 2~3시간 + 코드 수정 2시간 |
|
||||
| 적용 가능 | ✅ DataLoader sampler 수정으로 가능 |
|
||||
|
||||
---
|
||||
|
||||
## 2. "Less is More" 전략 (LIMA, AlpaGasus)
|
||||
|
||||
### 핵심 논문
|
||||
- **LIMA (Zhou et al., 2023)**: 1000개 고품질 > 52k 저품질. 65B 모델에서 검증.
|
||||
- **AlpaGasus (Chen et al., 2023)**: GPT-4로 품질 점수 → 9k에서 3k 선별 → Alpaca 대비 우수
|
||||
- **DEITA (Liu et al., 2024)**: complexity + quality + diversity 3축 필터링
|
||||
|
||||
### 품질 점수 계산 방법 (외부 API 없이)
|
||||
|
||||
```python
|
||||
# scripts/quality_filter.py
|
||||
import json, math, torch
|
||||
import numpy as np
|
||||
from collections import Counter
|
||||
|
||||
def compute_quality_scores(data_path, model, tokenizer, device="cuda:0"):
|
||||
"""다차원 품질 점수 계산"""
|
||||
with open(data_path) as f:
|
||||
samples = [json.loads(line) for line in f]
|
||||
|
||||
scored = []
|
||||
model.eval()
|
||||
|
||||
for i, sample in enumerate(samples):
|
||||
msgs = sample["messages"]
|
||||
|
||||
# 1) 길이 점수: 너무 짧거나 너무 긴 건 감점
|
||||
response = "".join(m["content"] for m in msgs if m["role"] == "assistant")
|
||||
resp_len = len(response)
|
||||
len_score = min(resp_len / 500, 1.0) * (1.0 if resp_len < 3000 else 3000 / resp_len)
|
||||
|
||||
# 2) 반복 감점: n-gram 반복률
|
||||
tokens = list(response)
|
||||
if len(tokens) > 10:
|
||||
trigrams = [tuple(tokens[j:j+3]) for j in range(len(tokens)-2)]
|
||||
unique_ratio = len(set(trigrams)) / len(trigrams)
|
||||
else:
|
||||
unique_ratio = 1.0
|
||||
rep_score = unique_ratio
|
||||
|
||||
# 3) Perplexity 점수 (중간이 좋음 - 너무 낮으면 trivial, 너무 높으면 noise)
|
||||
# 사전 계산된 ppl 사용
|
||||
|
||||
# 4) Instruction 복잡도: instruction 길이
|
||||
instruction = "".join(m["content"] for m in msgs if m["role"] == "user")
|
||||
inst_complexity = min(len(instruction) / 200, 1.0)
|
||||
|
||||
# 종합 점수
|
||||
quality = 0.3 * len_score + 0.3 * rep_score + 0.2 * inst_complexity + 0.2
|
||||
scored.append({"idx": i, "quality": quality, "sample": sample})
|
||||
|
||||
return scored
|
||||
|
||||
def select_top_k(scored, k):
|
||||
"""상위 k개 선별"""
|
||||
scored.sort(key=lambda x: x["quality"], reverse=True)
|
||||
return scored[:k]
|
||||
```
|
||||
|
||||
### 권장 샘플 수
|
||||
- 188k 전체 → **50k~80k** 권장 (상위 30~40%)
|
||||
- 1B 모델 규모에서는 LIMA처럼 극단적 축소보다 moderate 필터링이 적합
|
||||
- 근거: AlpaGasus는 ~30% 선별에서 최적, 1B는 65B보다 데이터 의존도 높음
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 반복률 -30~50%, ko_ifeval +3~5% |
|
||||
| 구현 복잡도 | 2/5 |
|
||||
| 소요 시간 | 품질 계산 3~4시간, 필터링 코드 1시간 |
|
||||
| 적용 가능 | ✅ 데이터 전처리 단계 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Packing (Sequence Packing)
|
||||
|
||||
### 개념
|
||||
짧은 시퀀스들을 하나의 max_seq_len에 패킹하여 padding 낭비 제거.
|
||||
|
||||
### 현 프로젝트 상황
|
||||
- 이미 `dynamic_collate_fn`으로 batch-level dynamic padding 구현됨
|
||||
- Packing은 그 이상: **여러 샘플을 하나의 시퀀스로 concatenate**
|
||||
|
||||
### 주의사항: Cross-contamination
|
||||
- 패킹된 서로 다른 샘플 간 attention이 흐르면 안 됨
|
||||
- **해결**: Flash Attention v2의 `cu_seqlens` 파라미터 (varlen attention)
|
||||
- 또는 block diagonal attention mask
|
||||
|
||||
### 구현 방법
|
||||
```python
|
||||
# data/packed_sft_dataset.py
|
||||
class PackedSFTDataset:
|
||||
"""여러 SFT 샘플을 하나의 시퀀스로 패킹"""
|
||||
|
||||
def __init__(self, samples, tokenizer, max_seq_len=4096):
|
||||
self.packed = []
|
||||
self.cu_seqlens = [] # Flash Attention varlen용
|
||||
|
||||
buffer_ids = []
|
||||
buffer_labels = []
|
||||
seq_lens = []
|
||||
|
||||
for sample in samples:
|
||||
ids, labels = self._tokenize(sample, tokenizer)
|
||||
|
||||
if len(buffer_ids) + len(ids) > max_seq_len:
|
||||
# 현재 버퍼 저장
|
||||
if buffer_ids:
|
||||
self._save_buffer(buffer_ids, buffer_labels, seq_lens, max_seq_len)
|
||||
buffer_ids = ids
|
||||
buffer_labels = labels
|
||||
seq_lens = [len(ids)]
|
||||
else:
|
||||
buffer_ids.extend(ids)
|
||||
buffer_labels.extend(labels)
|
||||
seq_lens.append(len(ids))
|
||||
|
||||
if buffer_ids:
|
||||
self._save_buffer(buffer_ids, buffer_labels, seq_lens, max_seq_len)
|
||||
|
||||
def _save_buffer(self, ids, labels, seq_lens, max_seq_len):
|
||||
# Pad to max_seq_len
|
||||
pad_len = max_seq_len - len(ids)
|
||||
ids = ids + [0] * pad_len
|
||||
labels = labels + [-1] * pad_len
|
||||
|
||||
# cu_seqlens for varlen flash attention
|
||||
cu = [0]
|
||||
for l in seq_lens:
|
||||
cu.append(cu[-1] + l)
|
||||
|
||||
self.packed.append({
|
||||
"input_ids": torch.tensor(ids),
|
||||
"labels": torch.tensor(labels),
|
||||
"cu_seqlens": torch.tensor(cu, dtype=torch.int32),
|
||||
})
|
||||
```
|
||||
|
||||
### 속도 개선 예상
|
||||
- 현재 평균 시퀀스 길이가 max_seq_len(4096)보다 훨씬 짧다면 **1.5~3× 속도 향상**
|
||||
- SFT 데이터 특성상 평균 ~1000 토큰이면 ~3× 효율 향상 예상
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 학습 속도 1.5~3×, 성능 변화 없거나 미미 |
|
||||
| 구현 복잡도 | 3/5 (Flash Attention varlen 연동 필요) |
|
||||
| 소요 시간 | 1~2일 |
|
||||
| 적용 가능 | ⚠️ 모델의 attention 구현이 cu_seqlens 지원해야 함 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Multi-task SFT (도메인별 Loss Weighting)
|
||||
|
||||
### 개념
|
||||
데이터 소스별로 도메인을 분류하고, 도메인별 loss weight를 다르게 적용.
|
||||
|
||||
### 현재 데이터 소스 분석 (추정)
|
||||
- `korean_safe_conv/raw/` 하위: hatespeech, square, evol, yitingxie, gamseong, koalpaca, conversation
|
||||
- 카테고리: 안전성, QA, 창작, 일반 대화
|
||||
|
||||
### 구현 방법
|
||||
```python
|
||||
# 도메인 태그를 JSONL에 추가
|
||||
# {"messages": [...], "domain": "qa"}
|
||||
# {"messages": [...], "domain": "creative"}
|
||||
|
||||
# trainer에서 도메인별 loss weight
|
||||
DOMAIN_WEIGHTS = {
|
||||
"qa": 1.0,
|
||||
"creative": 0.8,
|
||||
"safety": 1.2,
|
||||
"code": 1.0,
|
||||
"math": 1.0,
|
||||
"conversation": 0.6, # 일반 대화는 낮게
|
||||
}
|
||||
```
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 특정 벤치마크 +1~3% |
|
||||
| 구현 복잡도 | 3/5 |
|
||||
| 소요 시간 | 도메인 분류 0.5일 + 구현 0.5일 |
|
||||
| 적용 가능 | ✅ loss 계산 시 weight 곱셈 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Token-level Loss Weighting / Focal Loss
|
||||
|
||||
### 개념
|
||||
모든 response 토큰에 동일 weight 대신, 모델이 예측하기 어려운 토큰에 더 높은 weight.
|
||||
|
||||
### Focal Loss 구현
|
||||
```python
|
||||
# train/focal_loss.py
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
def focal_cross_entropy(logits, targets, gamma=2.0, ignore_index=-1):
|
||||
"""
|
||||
Focal loss: down-weight easy tokens, up-weight hard tokens.
|
||||
Lin et al., "Focal Loss for Dense Object Detection", ICCV 2017.
|
||||
"""
|
||||
# Standard CE
|
||||
ce_loss = F.cross_entropy(
|
||||
logits.reshape(-1, logits.size(-1)),
|
||||
targets.reshape(-1),
|
||||
ignore_index=ignore_index,
|
||||
reduction='none'
|
||||
)
|
||||
|
||||
# p_t = probability of correct class
|
||||
log_pt = -ce_loss
|
||||
pt = torch.exp(log_pt)
|
||||
|
||||
# Focal weight: (1 - p_t)^gamma
|
||||
focal_weight = (1 - pt) ** gamma
|
||||
loss = focal_weight * ce_loss
|
||||
|
||||
# Mask ignored tokens
|
||||
mask = (targets.reshape(-1) != ignore_index)
|
||||
loss = loss[mask].mean()
|
||||
|
||||
return loss
|
||||
```
|
||||
|
||||
### 적용: trainer.py 수정
|
||||
```python
|
||||
# trainer.py의 _compute_loss에서
|
||||
# 기존: F.cross_entropy(logits, targets, ignore_index=-1)
|
||||
# 변경: focal_cross_entropy(logits, targets, gamma=2.0, ignore_index=-1)
|
||||
```
|
||||
|
||||
### 실제 효과
|
||||
- SFT에서 focal loss 적용 논문은 제한적
|
||||
- **SelectIT (Liu et al., 2024)**: token-level selection으로 IFEval +2~4%
|
||||
- gamma=2.0이 일반적이나 SFT에서는 gamma=1.0~1.5 권장 (너무 강하면 불안정)
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | ko_ifeval +1~3%, 반복률 영향 미미 |
|
||||
| 구현 복잡도 | 1/5 |
|
||||
| 소요 시간 | 2시간 |
|
||||
| 적용 가능 | ✅ loss 함수만 교체 |
|
||||
|
||||
---
|
||||
|
||||
## 6. Data Augmentation for Korean
|
||||
|
||||
### 방법 A: Self-Paraphrase
|
||||
```bash
|
||||
# 현재 모델(또는 더 큰 모델)로 response 재생성
|
||||
# instruction은 유지, output만 다양화
|
||||
# → 동일 instruction에 대한 N개 다른 응답 확보
|
||||
```
|
||||
|
||||
### 방법 B: Back-translation
|
||||
```python
|
||||
# 영어 고품질 데이터 (Alpaca, Dolly, OpenAssistant) → 한국어 번역
|
||||
# 서버에서 실행 가능한 방법:
|
||||
# 1) NLLB-200 3.3B (Meta): 오프라인 번역, 1GPU로 실행 가능
|
||||
# 2) 한국어 특화 모델로 직접 번역 생성
|
||||
|
||||
# pip install transformers
|
||||
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
|
||||
|
||||
model_name = "facebook/nllb-200-3.3B"
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||
model = AutoModelForSeq2SeqLM.from_pretrained(model_name).to("cuda:7") # 여유 GPU 1개 사용
|
||||
|
||||
def translate_en_to_ko(text):
|
||||
inputs = tokenizer(text, return_tensors="pt", max_length=1024, truncation=True).to("cuda:7")
|
||||
outputs = model.generate(**inputs, forced_bos_token_id=tokenizer.lang_code_to_id["kor_Hang"], max_new_tokens=1024)
|
||||
return tokenizer.decode(outputs[0], skip_special_tokens=True)
|
||||
```
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 다양성 증가, ko_ifeval +2~4% (좋은 소스 데이터 시) |
|
||||
| 구현 복잡도 | 3/5 |
|
||||
| 소요 시간 | 번역 파이프라인 1일 + 번역 실행 1~2일 |
|
||||
| 적용 가능 | ✅ 별도 GPU에서 병렬 실행 가능 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 학습 안정성 개선
|
||||
|
||||
### FP8 학습 주의사항
|
||||
현재 설정: MXFP8 + BF16 기반. 주요 주의점:
|
||||
|
||||
1. **Loss spike 방지**
|
||||
- `max_grad_norm: 1.0` 이미 적용됨 ✅
|
||||
- LR 2e-5는 보수적 ✅
|
||||
- 추가: gradient norm 모니터링 + 자동 LR 감소
|
||||
```python
|
||||
# 연속 3번 grad_norm > threshold면 lr 반감
|
||||
if grad_norm > 5.0:
|
||||
spike_count += 1
|
||||
if spike_count >= 3:
|
||||
for pg in optimizer.param_groups:
|
||||
pg['lr'] *= 0.5
|
||||
```
|
||||
|
||||
2. **Weight Decay**
|
||||
- 현재 0.01 (적절)
|
||||
- SFT에서는 0.01~0.05 범위가 표준
|
||||
|
||||
3. **Dropout**
|
||||
- 현재 `dropout: 0.0` — SFT에서는 **0.05~0.1 추가 권장**
|
||||
- 과적합 방지에 직접적 효과
|
||||
```yaml
|
||||
dropout: 0.05 # configs/korean_1b_sft.yaml
|
||||
```
|
||||
|
||||
4. **FP8 amax 설정**
|
||||
- `fp8_amax_history_len: 16` + `fp8_amax_compute_algo: "max"` — 적절
|
||||
- MXFP8은 DelayedScaling보다 안정적
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 안정성 ↑, 과적합 -10~20%, 반복률 -5~10% |
|
||||
| 구현 복잡도 | 1/5 |
|
||||
| 소요 시간 | 1시간 |
|
||||
| 적용 가능 | ✅ config 변경만으로 가능 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 평가 기반 데이터 선택 (Self-Play → ORPO)
|
||||
|
||||
### 파이프라인
|
||||
```
|
||||
1. 현재 SFT 모델로 각 instruction에 대해 N=4개 응답 생성
|
||||
2. 자동 평가 (반복률, 길이, 일관성)로 best/worst 선정
|
||||
3. (chosen, rejected) 페어 구성
|
||||
4. ORPO/DPO 학습 (이미 train/orpo.py 존재!)
|
||||
```
|
||||
|
||||
### 구체적 단계
|
||||
```python
|
||||
# scripts/generate_self_play.py
|
||||
def generate_candidates(model, tokenizer, instructions, n=4, temp=0.8):
|
||||
pairs = []
|
||||
for inst in instructions:
|
||||
responses = []
|
||||
for _ in range(n):
|
||||
out = model.generate(inst, temperature=temp, max_new_tokens=1024)
|
||||
score = auto_evaluate(out) # 반복률, 길이, coherence
|
||||
responses.append((out, score))
|
||||
|
||||
responses.sort(key=lambda x: x[1], reverse=True)
|
||||
pairs.append({
|
||||
"instruction": inst,
|
||||
"chosen": responses[0][0],
|
||||
"rejected": responses[-1][0],
|
||||
})
|
||||
return pairs
|
||||
```
|
||||
|
||||
### 평가
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 예상 효과 | 반복률 -40~60%, ko_ifeval +3~5% |
|
||||
| 구현 복잡도 | 4/5 |
|
||||
| 소요 시간 | 생성 1~2일 + ORPO 학습 0.5일 |
|
||||
| 적용 가능 | ✅ orpo.py 이미 존재 |
|
||||
|
||||
---
|
||||
|
||||
## 종합 비교표
|
||||
|
||||
| 기법 | 예상 효과 (반복률) | 예상 효과 (ko_ifeval) | 구현 복잡도 | 소요 시간 | 우선순위 |
|
||||
|------|-------------------|---------------------|------------|----------|---------|
|
||||
| 1. Curriculum Learning | - | +1~2% | 2/5 | 5시간 | 중기 |
|
||||
| 2. Less is More | **-30~50%** | **+3~5%** | 2/5 | 5시간 | **즉시** |
|
||||
| 3. Packing | (속도만) | (변화없음) | 3/5 | 1~2일 | 중기 |
|
||||
| 4. Multi-task Weighting | - | +1~3% | 3/5 | 1일 | 중기 |
|
||||
| 5. Focal Loss | - | +1~3% | **1/5** | **2시간** | **즉시** |
|
||||
| 6. Data Augmentation | - | +2~4% | 3/5 | 2~3일 | 중기 |
|
||||
| 7. 학습 안정성 (dropout) | **-5~10%** | - | **1/5** | **1시간** | **즉시** |
|
||||
| 8. Self-Play → ORPO | **-40~60%** | **+3~5%** | 4/5 | 2~3일 | 중기 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 즉시 적용 Top 3
|
||||
|
||||
### 1위: "Less is More" 데이터 필터링
|
||||
- **근거**: LIMA, AlpaGasus 논문에서 일관되게 입증. 188k → 50~80k 필터링으로 저품질/반복적 샘플 제거
|
||||
- **예상 효과**: 반복률 -30~50%, ko_ifeval +3~5%
|
||||
- **소요**: 5시간 (PPL 계산 + 필터링 스크립트)
|
||||
- **리스크**: 낮음 (최악의 경우 전체 데이터로 롤백)
|
||||
|
||||
### 2위: Focal Loss 적용
|
||||
- **근거**: 어려운 토큰에 집중 → instruction following 능력 향상. 구현 극히 간단
|
||||
- **예상 효과**: ko_ifeval +1~3%
|
||||
- **소요**: 2시간 (loss 함수 1개 추가)
|
||||
- **리스크**: 매우 낮음 (gamma 값만 조정하면 됨)
|
||||
|
||||
### 3위: Dropout 추가 (0.05)
|
||||
- **근거**: 현재 dropout=0.0으로 과적합 위험. SFT에서 light dropout은 표준
|
||||
- **예상 효과**: 과적합 감소, 반복률 -5~10%
|
||||
- **소요**: config 한 줄 변경
|
||||
- **리스크**: 없음
|
||||
|
||||
---
|
||||
|
||||
## 📅 중기 적용 Top 3
|
||||
|
||||
### 1위: Self-Play → ORPO (SFT 이후)
|
||||
- **근거**: SFT 완료 후 선호 학습은 반복률 감소에 가장 효과적. orpo.py 이미 구현됨
|
||||
- **예상 효과**: 반복률 -40~60%, ko_ifeval +3~5%
|
||||
- **소요**: 2~3일 (생성 + 학습)
|
||||
|
||||
### 2위: Sequence Packing
|
||||
- **근거**: 학습 속도 1.5~3× 향상. 향후 반복 실험에 필수적
|
||||
- **예상 효과**: 학습 시간 대폭 단축
|
||||
- **소요**: 1~2일
|
||||
|
||||
### 3위: Curriculum Learning + Data Augmentation (Back-translation)
|
||||
- **근거**: 데이터 다양성과 학습 효율 동시 개선
|
||||
- **예상 효과**: ko_ifeval +2~4%
|
||||
- **소요**: 3~4일
|
||||
|
||||
---
|
||||
|
||||
## 권장 실행 순서
|
||||
|
||||
```
|
||||
Phase 1 (즉시, SFT 전):
|
||||
1. dropout: 0.05 설정
|
||||
2. 데이터 품질 필터링 (188k → 60~80k)
|
||||
3. Focal loss 적용 (gamma=1.5)
|
||||
→ SFT 실행
|
||||
|
||||
Phase 2 (SFT 후):
|
||||
4. Self-Play 데이터 생성
|
||||
5. ORPO 학습
|
||||
|
||||
Phase 3 (다음 라운드):
|
||||
6. Packing 구현 (반복 실험 가속)
|
||||
7. Back-translation으로 데이터 확장
|
||||
8. Curriculum learning 실험
|
||||
```
|
||||
258
source/eval/plan/pipeline_design.md
Normal file
258
source/eval/plan/pipeline_design.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# SFT 파이프라인 완전 재설계 보고서
|
||||
|
||||
> 작성일: 2026-02-26
|
||||
> 목표: 반복률 <5%, ko_ifeval 20-30%
|
||||
> 모델: 1B 한국어 LLM, 188K 학습 샘플
|
||||
|
||||
---
|
||||
|
||||
## 1. 현재 코드 검증 결과
|
||||
|
||||
### ✅ 올바르게 수정된 것들
|
||||
|
||||
| 항목 | 상태 | 근거 |
|
||||
|------|------|------|
|
||||
| Dynamic padding | ✅ 정상 | `SFTDataset.__getitem__`이 raw-length 텐서 반환, `dynamic_collate_fn`이 batch-local max + 64-align 패딩 |
|
||||
| EOS 강제 부착 | ✅ 정상 | truncation 시 `response_ids[-1] = self.eos_token_id` 강제 |
|
||||
| Enhanced quality filter | ✅ 적용됨 | `_enhanced_quality_filter`가 `quality_filter()` 안에서 호출됨 (EOS 리터럴, Q/A 오염, 50자 미만) |
|
||||
| Val data 연결 | ✅ 정상 | `launch_sft.sh`에 `VAL_DATA="data/sft/val.jsonl"`, `--val_data` 전달됨 |
|
||||
| Val loss 기록 | ✅ 구현됨 | `trainer.py`의 `_run_validation()` + `eval_interval=250` step마다 실행 |
|
||||
| Best checkpoint | ✅ 구현됨 | `val_loss < self._best_val_loss` 시 suffix="best" 저장 |
|
||||
| NEFTune | ✅ 적용됨 | `noise_alpha=10.0`으로 embedding hook 등록 |
|
||||
| Gradient checkpointing | ✅ 적용됨 | `model.gradient_checkpointing_enable()` 호출 |
|
||||
|
||||
### ⚠️ 남은 문제 (수정 필요)
|
||||
|
||||
#### 🔴 P0: Labels off-by-one 버그
|
||||
|
||||
```python
|
||||
# sft_dataset.py line ~168
|
||||
resp_label_start = max(0, resp_start - 1) # ← 이게 문제
|
||||
resp_label_end = resp_label_start + len(response_ids)
|
||||
labels[resp_label_start:resp_label_end] = torch.tensor(response_ids, dtype=torch.long)
|
||||
```
|
||||
|
||||
**문제**: `resp_start - 1`은 프롬프트의 마지막 토큰 위치에 response_ids[0]을 넣음.
|
||||
이는 next-token prediction에서 "프롬프트 마지막 토큰이 들어오면 response 첫 토큰을 예측하라"는 의미로 **의도적 설계**일 수 있음.
|
||||
|
||||
**그러나**: 이 경우 labels의 길이가 `len(response_ids)`인데 `resp_label_start`부터 시작하므로 response의 마지막 토큰(EOS)에 대한 label이 한 칸 앞으로 밀려, **마지막 response 토큰의 label이 누락될 수 있음**.
|
||||
|
||||
실제로:
|
||||
- `resp_start = len(prompt_ids)` (예: 50)
|
||||
- `resp_label_start = 49`
|
||||
- `resp_label_end = 49 + len(response_ids)`
|
||||
- `labels[49:49+R] = response_ids[0:R]`
|
||||
|
||||
이렇게 하면 position 49의 label = response_ids[0], position 50의 label = response_ids[1], ... 이건 autoregressive LM 관점에서:
|
||||
- position 49 입력 = prompt 마지막 토큰 → 예측해야 할 것 = response 첫 토큰 ✅
|
||||
- position (49+R-1) 입력 = response[R-2] → 예측해야 할 것 = response[R-1] (=EOS) ✅
|
||||
- position (49+R) 입력 = response[R-1] (=EOS) → label = -1 (무시) ✅
|
||||
|
||||
**결론: 이 로직은 실제로 올바름.** next-token prediction의 teacher-forcing에서 shift가 모델 내부가 아닌 labels에서 이루어지는 패턴. 단, **모델의 forward가 logits[t]로 position t+1을 예측하는지 확인 필요**. 대부분 decoder LM은 `loss = CE(logits[:, :-1], labels[:, 1:])` 형태를 씀.
|
||||
|
||||
**확인 사항**: `trainer.py`의 `_compute_loss`를 보면:
|
||||
```python
|
||||
nn.functional.cross_entropy(logits.view(B*T, V), targets.view(B*T), ignore_index=-1)
|
||||
```
|
||||
여기서 **shift가 없음**. 즉 `logits[t]`와 `targets[t]`를 직접 비교함.
|
||||
|
||||
일반적으로 decoder LM의 logits[t]는 position t의 hidden state로 t+1번째 토큰을 예측. 따라서:
|
||||
- `logits[t]` = position t 다음에 올 토큰 예측
|
||||
- `targets[t]`가 t+1 토큰이어야 함
|
||||
|
||||
**현재 코드의 labels 배치:**
|
||||
- `labels[resp_start - 1] = response_ids[0]` → logits[resp_start-1]로 response[0] 예측 ✅ (prompt 마지막 토큰 → 첫 응답)
|
||||
- `labels[resp_start - 1 + k] = response_ids[k]` → logits[prompt_last + k]로 response[k] 예측
|
||||
|
||||
이것이 맞으려면 `input_ids[resp_start - 1 + k]`가 response_ids[k]의 **이전** 토큰이어야 함.
|
||||
- k=0: `input_ids[resp_start - 1]` = prompt 마지막 토큰, label = response[0] ✅
|
||||
- k=1: `input_ids[resp_start]` = response[0], label = response[1] ✅
|
||||
- k=R-1: `input_ids[resp_start + R - 2]` = response[R-2], label = response[R-1] = EOS ✅
|
||||
|
||||
**결론: off-by-one이 아님. 이 코드는 shift를 labels 측에서 수행하는 정상적인 패턴.**
|
||||
|
||||
#### 🟡 P1: Early stopping 미구현
|
||||
|
||||
현재 best checkpoint은 저장하지만 **학습을 중단하는 로직은 없음**. val_loss가 N번 연속 개선 안 되면 중단하는 patience 로직 추가 권장.
|
||||
|
||||
#### 🟡 P2: `_quality_filter`의 한국어 비율 30%는 너무 느슨
|
||||
|
||||
output의 한국어 문자 비율 30%는 영어/코드가 대부분인 샘플도 통과시킴. 한국어 SFT 목적에는 **50% 이상** 권장.
|
||||
|
||||
#### 🟢 P3: weight_decay=0.01이 launch_sft.sh에 명시 안 됨
|
||||
|
||||
`sft.py`의 default=0.01을 그대로 사용 중. 명시적으로 적는 게 좋음.
|
||||
|
||||
---
|
||||
|
||||
## 2. 데이터 파이프라인 설계
|
||||
|
||||
### 2.1 포맷 확인
|
||||
|
||||
현재 `sft_dataset.py`의 chat template:
|
||||
```
|
||||
<|user|>
|
||||
{instruction}
|
||||
<|assistant|>
|
||||
{output}</s>
|
||||
```
|
||||
|
||||
**→ 목표 포맷과 일치함.** 추가 수정 불필요.
|
||||
|
||||
### 2.2 샘플 품질 기준 최종안
|
||||
|
||||
| 기준 | 현재값 | 권장값 | 근거 |
|
||||
|------|--------|--------|------|
|
||||
| 최소 output 길이 | 50자 | **100자** | 50자는 1-2문장 수준으로 너무 짧음. IF-eval 같은 벤치마크는 구조화된 답변 필요 |
|
||||
| 최대 output 길이 | 4000자 | **3000자** | 극단적으로 긴 응답은 반복 퇴화 리스크 ↑ |
|
||||
| 한국어 비율 | 30% | **50%** | 한국어 LLM 목적에 30%는 너무 느슨 |
|
||||
| 3-gram 반복 unique ratio | <0.5 제거 | **<0.6 제거** | 더 엄격한 반복 필터링으로 반복률 <5% 달성 |
|
||||
| 최소 instruction 길이 | 10자 | **15자** | 극히 짧은 지시문은 모호한 학습 신호 |
|
||||
|
||||
### 2.3 데이터 믹싱 전략
|
||||
|
||||
현재 가중치 & 권장 조정:
|
||||
|
||||
| 소스 | 현재 가중치 | 권장 가중치 | 비고 |
|
||||
|------|------------|------------|------|
|
||||
| KOR-OpenOrca-Platypus-v3 | 2.0 | **1.5** | 여전히 과대표집 가능. 다양성 확보 위해 축소 |
|
||||
| kullm-v2 | 1.0 | **1.0** | 유지 |
|
||||
| ko-alpaca-12k | 2.0 | **1.5** | 소규모 고품질이지만 2배는 과도. 1.5배 적정 |
|
||||
| korean_safe_conversation | 1.5 | **1.5** | 안전 정렬 중요, 유지 |
|
||||
| evol-instruct-korean | 1.5 | **2.0** | 복잡한 추론/코드 능력은 IF-eval에 직결. 상향 |
|
||||
| kovast | 0.8 | **0.5** | 멀티턴 첫 턴만 추출하므로 품질 불안정, 더 축소 |
|
||||
|
||||
**도메인 균형 목표 (대략적):**
|
||||
- 일반 지식/QA: ~35%
|
||||
- 안전/정렬: ~15%
|
||||
- 추론/코드/수학: ~25% (IF-eval 핵심)
|
||||
- 대화/창작: ~15%
|
||||
- 기타: ~10%
|
||||
|
||||
---
|
||||
|
||||
## 3. 하이퍼파라미터 최종 확정
|
||||
|
||||
### 3.1 설정 배경
|
||||
|
||||
- 모델: 1B params
|
||||
- 데이터: ~188K samples
|
||||
- GPU: 8× B200 (FP8)
|
||||
- Effective batch: 4 × 2 (grad_accum) × 8 (GPU) = **64 sequences/step**
|
||||
- Steps per epoch: 188K / 64 ≈ **2,940 steps**
|
||||
- 5000 steps ≈ **1.7 epochs**
|
||||
|
||||
### 3.2 최종 권장값
|
||||
|
||||
| 파라미터 | 현재값 | **최종 권장값** | 학술 근거 |
|
||||
|----------|--------|----------------|-----------|
|
||||
| **learning_rate** | 2e-5 | **2e-5** | Alpaca, Vicuna, LIMA 등 1-7B SFT 표준. 1e-5는 너무 보수적 (underfitting), 5e-6는 1B에 비현실적으로 낮음 |
|
||||
| **epochs** | ~1.7 (5000 steps) | **3 epochs (~8,820 steps)** | 188K는 중규모. 3 epoch이면 충분한 학습 + overfitting 방지. Stanford Alpaca도 3 epoch 사용 |
|
||||
| **max_steps** | 5000 | **9,000** | 3 epoch + 약간 여유 |
|
||||
| **warmup_steps** | 150 | **300** | 9000 steps의 ~3%. Llama-2-chat 논문 기준 2-5% warmup 권장 |
|
||||
| **weight_decay** | 0.01 | **0.01** | SFT 표준. pretrain(0.1)보다 낮아야 함. 적절 |
|
||||
| **max_seq_len** | 4096 | **4096** | dynamic padding이 있으므로 4096 유지해도 짧은 샘플에서 낭비 없음. 긴 응답 학습 가능 |
|
||||
| **max_grad_norm** | 1.0 | **1.0** | 표준값. 변경 불필요 |
|
||||
| **batch_size (per GPU)** | 4 | **4** | B200 메모리 충분. 유지 |
|
||||
| **grad_accum** | 2 | **2** | eff_batch=64 적절 |
|
||||
| **NEFTune alpha** | 10.0 | **5.0** | 원 논문 default=5. 10은 aggressive. 반복률 개선에 도움되지만 과도하면 품질 하락 |
|
||||
| **eval_interval** | 250 | **500** | 9000 steps에서 250은 너무 빈번. 500이면 ~18회 평가 |
|
||||
| **save_interval** | 500 | **1000** | 디스크 절약 |
|
||||
|
||||
### 3.3 Launch script 수정사항
|
||||
|
||||
```bash
|
||||
MAX_STEPS=9000
|
||||
WARMUP_STEPS=300
|
||||
# NEFTune alpha는 sft.py 코드에서 10→5로 변경 필요
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 학습 모니터링 강화
|
||||
|
||||
### 4.1 현재 구현 상태
|
||||
|
||||
| 기능 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| Val loss 주기적 기록 | ✅ eval_interval=250마다 | TensorBoard + stdout |
|
||||
| Best checkpoint 저장 | ✅ val_loss 기준 | suffix="best" |
|
||||
| TensorBoard 로깅 | ✅ loss, lr, grad_norm, tok/s | |
|
||||
| Early stopping | ❌ 미구현 | **추가 필요** |
|
||||
| 반복률 추적 | ❌ 미구현 | 학습 중 실시간 추적은 비현실적 |
|
||||
|
||||
### 4.2 Early Stopping 구현 방안
|
||||
|
||||
`trainer.py`에 추가할 로직:
|
||||
|
||||
```python
|
||||
# __init__에 추가
|
||||
self._patience = 5 # eval_interval * patience = 5 * 500 = 2500 steps 동안 개선 없으면 중단
|
||||
self._no_improve_count = 0
|
||||
|
||||
# _run_validation 후 (train 메서드 내)
|
||||
if val_loss < self._best_val_loss:
|
||||
self._best_val_loss = val_loss
|
||||
self._no_improve_count = 0
|
||||
else:
|
||||
self._no_improve_count += 1
|
||||
if self._no_improve_count >= self._patience:
|
||||
self._log(f"Early stopping at step {step+1} (no improvement for {self._patience} evals)")
|
||||
break # training loop 탈출
|
||||
```
|
||||
|
||||
### 4.3 반복률 추적
|
||||
|
||||
학습 중 실시간 반복률 측정은 generation이 필요해 비용이 큼. 대안:
|
||||
- **eval_interval마다 5개 샘플 생성** → 3-gram 반복 비율 계산 → TensorBoard 기록
|
||||
- 이는 선택사항이며, 최종 평가 시 체크포인트별로 측정하는 것이 현실적
|
||||
|
||||
---
|
||||
|
||||
## 5. 재학습 전 체크리스트
|
||||
|
||||
### 데이터
|
||||
- [ ] `data/sft/train.jsonl` 존재 확인 (188,234 lines) ✅
|
||||
- [ ] `data/sft/val.jsonl` 존재 확인 (9,907 lines) ✅
|
||||
- [ ] 데이터 품질 필터 재적용 여부 결정 (한국어 비율 50%, min_output 100자로 강화 시 재실행 필요)
|
||||
- [ ] 재실행 시: `python data/prepare_sft_data.py --min_output_len 100`
|
||||
|
||||
### 코드 수정
|
||||
- [ ] `prepare_sft_data.py`: 한국어 비율 0.3 → **0.5**로 변경
|
||||
- [ ] `prepare_sft_data.py`: `_quality_filter`의 max output 4000 → **3000** 변경
|
||||
- [ ] `prepare_sft_data.py`: DATASET_WEIGHTS 조정 (OpenOrca 2.0→1.5, evol-instruct 1.5→2.0, kovast 0.8→0.5)
|
||||
- [ ] `sft.py`: NEFTune alpha 10.0 → **5.0** 변경
|
||||
- [ ] `trainer.py`: Early stopping patience=5 로직 추가
|
||||
- [ ] `launch_sft.sh`: MAX_STEPS=9000, WARMUP_STEPS=300 변경
|
||||
|
||||
### 인프라
|
||||
- [ ] GPU 상태 확인: `nvidia-smi` (8× B200 가용)
|
||||
- [ ] 디스크 공간: 체크포인트당 ~4GB × ~9개 = ~36GB 필요
|
||||
- [ ] Base checkpoint 존재: `checkpoints/korean_1b_fp8_run1/checkpoint-0034000`
|
||||
- [ ] Tokenizer 존재 확인
|
||||
|
||||
### 학습 실행
|
||||
- [ ] TensorBoard 확인: `tensorboard --logdir checkpoints/korean_1b_sft/tensorboard`
|
||||
- [ ] 처음 100 step에서 loss 급감 확인 (정상이면 2-3 → 1.5 이하)
|
||||
- [ ] 500 step에서 val_loss 확인
|
||||
- [ ] 반복 생성 테스트: 1000 step 체크포인트에서 간단한 프롬프트로 생성 테스트
|
||||
|
||||
---
|
||||
|
||||
## 6. 요약
|
||||
|
||||
### 현재 코드 상태: **양호 (minor 수정 6건)**
|
||||
|
||||
주요 버그는 이미 수정됨. labels off-by-one으로 보였던 것은 실제로는 정상적인 teacher-forcing shift 패턴. 추가 수정은 품질 향상을 위한 파라미터 튜닝 수준.
|
||||
|
||||
### 핵심 변경사항 우선순위
|
||||
|
||||
1. **MAX_STEPS 9000, WARMUP 300** (3 epoch 학습)
|
||||
2. **데이터 품질 필터 강화** (한국어 50%, min output 100자) → `prepare_sft_data.py` 재실행
|
||||
3. **Early stopping 구현**
|
||||
4. **NEFTune alpha 5.0으로 하향**
|
||||
5. **데이터 가중치 미세 조정**
|
||||
|
||||
### 예상 학습 시간
|
||||
|
||||
- 9000 steps × 8 GPU B200 FP8 ≈ **2-4시간** (seq_len 분포에 따라)
|
||||
Reference in New Issue
Block a user