# "처음부터 다시 시작" 시나리오 완전 분석

**작성일**: 2026-02-26  
**역할**: "처음부터 제대로 다시" 옹호자  
**결론**: ✅ **1B SFT 재학습 강력 권장 (40분), 3B 전환은 병렬 준비**

---

## 1. 현재 접근법의 근본적 한계

### 1.1 발견된 버그/문제가 가중치에 미친 영향

지금까지 발견된 문제들을 정리하면:

| # | 버그/문제 | 가중치 오염 정도 | 제거 가능? |
|---|-----------|-----------------|-----------|
| 1 | 프롬프트 포맷 불일치 (`### 질문:` vs `<\|user\|>`) | ❌ 가중치 무관 (추론 버그) | 추론 코드만 수정 |
| 2 | Dynamic padding 미작동 (4096 고정 패딩) | 🟡 간접 영향 — 학습 효율 저하로 실질 epoch 부족 | 재학습 필요 |
| 3 | 트렁케이션 시 EOS 손실 (0.04%) | 🟢 미미 (61/159K 샘플) | 코드 이미 수정됨 |
| 4 | `</s>` 리터럴 오염 데이터 113건 | 🟡 EOS 경계 혼란 유발 | 데이터 필터 필요 |
| 5 | Output 내 Q/A 마커 ~550건 | 🟡 자체 루프 패턴 학습 | 데이터 필터 필요 |
| 6 | OpenOrca 5배 업샘플링 → 과적합 | 🔴 가중치에 깊이 각인 | 재학습 필요 |
| 7 | Val split 없음 → 과적합 감지 불가 | — | 재학습 시 추가 |
| 8 | ~2 epoch만 학습 (업계 표준 3-5) | 🔴 underfitting | 재학습 필요 |
| 9 | 짧은 output 10.4% (50자 미만) | 🟡 EOS 타이밍 학습 불안정 | 데이터 필터 필요 |

### 1.2 "오염된 학습"의 가중치 잔류 여부

**결론: 부분적으로 남아있고, 완전 제거 불가능.**

SFT는 base model 위에 얇은 layer를 미세조정한 것이 아니라 **전체 가중치를 업데이트**한다. 5000 steps × lr=2e-5로 학습된 gradient update는 모든 layer에 분포되어 있으며:

- OpenOrca 5배 업샘플링으로 인해 해당 소스의 패턴이 **과도하게 각인**
- Q/A 마커 오염 데이터(550건)의 패턴도 가중치에 분산 저장
- `</s>` 리터럴이 포함된 113건이 EOS 토큰 예측 확률 분포를 왜곡

이들은 추가 학습(continual training)으로 "덮어쓸" 수는 있지만, **기존 오염을 정확히 역전시키는 것은 불가능**. 추가 학습은 새로운 gradient로 기존 가중치를 수정하지만, 이미 학습된 잘못된 패턴의 흔적(특히 low-rank subspace에서)은 완전히 사라지지 않는다.

### 1.3 반복 퇴화 17.7%: 파라미터 문제 vs 가중치 문제

수정 후 반복률 변화를 보면:

```
포맷 불일치 상태:        57% → 포맷 수정만으로 → 30.7% → +추론 파라미터 → 17.7%
```

**분석:**
- 57% → 30.7% (포맷 수정): **추론 버그** — 가중치 무관 ✅
- 30.7% → 17.7% (rep_penalty + no_repeat_ngram): **추론 파라미터** — 가중치 무관 ✅  
- **잔여 17.7%**: 이것이 **가중치 수준의 문제**

17.7%의 구성:
- 코드 설명 시 알파벳 나열 반복 (샘플 #2: 30.5%)
- 리스트형 답변에서 유사 항목 반복 (샘플 #4: 21.3%, #7: 24.4%, #8: 23.8%)
- 단순 사실 답변은 정상 (샘플 #1: 0.0%, #9: 13.3%)

**결론: 17.7%는 가중치 수준 문제.** 원인:
1. 학습 데이터 자체의 반복 패턴 (57건 직접 반복 + 수백 건 간접)
2. 2 epoch의 underfitting으로 EOS 생성 신뢰도 부족
3. OpenOrca 과잉 대표로 인한 다양성 결핍

---

## 2. 처음부터 다시 한다면: 구체적 개선 사항

### 2.1 SFT 데이터 파이프라인

| 항목 | 현재 | 재시작 시 |
|------|------|----------|
| 포맷 | `<\|user\|>/<\|assistant\|>` ✅ | 동일 유지 |
| EOS 처리 | 트렁케이션 시 손실 가능 | **코드 이미 수정됨** (`response_ids[-1] = eos_token_id`) |
| Dynamic padding | 미작동 (고정 4096) | **코드 이미 수정됨** (가변 길이 반환) |
| 품질 필터 | 기본 (50자, 30% 한글) | **강화**: 80자, 40% 한글, EOS/Q&A 오염 제거, 5-gram 반복 필터 |
| Val split | 없음 | **5% val split** (prepare_sft_data.py에 이미 구현됨) |
| 가중치 샘플링 | OpenOrca 5.0× | **OpenOrca 2.0×** (이미 수정됨) |
| 예상 데이터 | 159K | **~120-130K** (필터링 후) |

**핵심 변경: `prepare_sft_data.py`를 다시 실행하면 된다.** 코드에 이미 enhanced filter와 수정된 가중치가 반영되어 있다.

### 2.2 학습 하이퍼파라미터

| 파라미터 | 현재 | 재시작 시 | 근거 |
|---------|------|----------|------|
| max_steps | 5,000 (~2 epoch) | **7,500-10,000** (3-4 epoch) | 업계 표준 3-5 epoch |
| lr | 2e-5 | **2e-5** 유지 | 업계 표준, loss curve 안정 |
| warmup | 150 (3%) | **225-300** (3%) | steps 증가에 비례 |
| NEFTune alpha | 10.0 | **10.0** 유지 | 159K 데이터에 적합 |
| val_data | 없음 | **val.jsonl** 전달 | 과적합 모니터링 |
| save_interval | 500 | **500** 유지 | best checkpoint 선택 가능 |

### 2.3 추가 고려사항

- **`<|user|>` / `<|assistant|>` 특수 토큰 등록**: 현재 서브워드 분할됨. 단일 토큰으로 등록하면 더 robust하나 base model 재학습 필요 → **SFT에서는 현행 유지, 3B에서 반영**
- **Repetition penalty loss (Unlikelihood Training)**: 중기 옵션. 재시작 1차에는 데이터 품질 개선만으로 충분할 것

---

## 3. 업계 최고 수준 SFT 파이프라인 비교

### 3.1 주요 프레임워크 비교

| 기능 | 현 프로젝트 (수정 후) | LLaMA-Factory | TRL SFTTrainer | Axolotl |
|------|---------------------|---------------|----------------|---------|
| Completion-only loss | ✅ (labels=-1) | ✅ | ✅ (DataCollator) | ✅ |
| Dynamic padding | ✅ (수정됨) | ✅ | ✅ | ✅ |
| Sample packing | ❌ | ✅ | ✅ (`packing=True`) | ✅ |
| EOS 보장 | ✅ (수정됨) | ✅ | ✅ | ✅ |
| Val monitoring | ✅ (구현됨) | ✅ | ✅ | ✅ |
| Flash Attention | ✅ (64-align) | ✅ | ✅ | ✅ |
| NEFTune | ✅ | ✅ | ✅ | ✅ |

### 3.2 `packing=True` + `completion_only_loss` 분석

**Sample Packing**: 여러 짧은 샘플을 하나의 시퀀스에 연결하여 패딩 완전 제거.

```
Before packing (dynamic padding):
[sample1 (200 tok)] [pad pad pad ... (312 pad)] = 512 total
[sample2 (480 tok)] [pad pad pad ... (32 pad)]  = 512 total

After packing:
[sample1 (200 tok)][sample2 (480 tok)][pad ... (344)] = 1024 total
→ 2 samples in 1 sequence, less padding waste
```

**현 프로젝트 적용 가능성:**
- 평균 시퀀스 ~500 토큰이므로 packing 효과 **매우 큼** (4096 대비 88% 절약 → packing으로 추가 20-30% 절약)
- 그러나 구현 복잡도 높음: attention mask에 sample boundary 처리 필요
- **권장: 현재 dynamic padding만으로도 충분한 개선. Packing은 3B 또는 TRL 전환 시 도입.**

### 3.3 현 프로젝트에 바로 적용 가능한 것

1. ✅ **이미 적용됨**: Dynamic padding, EOS 보장, completion-only loss, NEFTune
2. 🟡 **미적용이나 중요도 낮음**: Sample packing (구현 복잡, 현재 효율 충분)
3. 🟡 **미적용이나 고려 가치**: TRL SFTTrainer 전환 (커스텀 LLM 클래스 호환성 확인 필요)

---

## 4. 3B 모델로의 전환 타이밍

### 4.1 1B 재학습 vs 바로 3B

| 기준 | 1B 재학습 | 바로 3B |
|------|----------|---------|
| 소요 시간 | ~40분 SFT | ~26시간 pretrain + ~2시간 SFT |
| 리스크 | 낮음 (검증된 파이프라인) | 중간 (새 아키텍처 설정 필요) |
| 기대 품질 | 반복률 17.7% → **5-8%** 예상 | 반복률 **2-5%** 예상 |
| ko_ifeval | 20-30% 예상 | 35-45% 예상 |
| 학습 검증 | 즉시 가능 | 26시간 후에야 확인 가능 |

### 4.2 Chinchilla Scaling Law 분석

```
Chinchilla 최적 학습 데이터 = 20 × 파라미터 수

1B 모델: 20 × 1B = 20B tokens (현재 ~8.91B → 부족하지만 SFT에는 충분)
3B 모델: 20 × 3B = 60B tokens (현재 데이터 ~150B → 충분)
         70 × 3B = 210B tokens (최적 → 150B로 71% 수준)
```

**현재 150B tokens 데이터는 3B 학습에 충분하다** (Chinchilla 최소 기준의 2.5배).

### 4.3 3B가 반복 퇴화를 구조적으로 덜 겪는가?

**예, 스케일 효과가 있다.** 근거:

1. **Representation capacity**: 3B는 1B 대비 ~2.5배 파라미터 → EOS 예측, 반복 회피 등 복잡한 패턴을 더 정확하게 학습
2. **Attention head 수 증가**: 더 많은 head가 "이전에 말한 것" 추적에 전용 가능
3. **경험적 증거**: Open Ko-LLM 리더보드에서 3B 모델들은 1B 대비 일관되게 반복률 낮음
4. **같은 SFT 데이터라도 3B가 더 잘 일반화**: 더 큰 모델이 same data에서 더 많은 패턴 추출

### 4.4 권장: **1B 재학습 먼저, 3B 병렬 준비**

```
Day 0: 데이터 재준비 (30분) + 1B SFT 재학습 (40분) = 오늘 완료
Day 0: 결과 평가 (30분) → 1B 기준선 확보
Day 1-2: 3B 아키텍처 설정 + pretrain 시작 (26시간)
Day 2-3: 3B SFT (2시간) + 평가
```

**이유:**
- 1B 재학습은 비용이 너무 낮다 (40분). 안 할 이유가 없다.
- 1B 결과로 파이프라인 검증 → 3B에 동일한 (검증된) 파이프라인 적용
- 3B pretrain 동안 1B 모델을 배포/데모에 사용 가능

---

## 5. "다시 시작"의 타임라인

### 5.1 상세 타임라인

| 단계 | 작업 | 소요 시간 | 누적 |
|------|------|----------|------|
| **A. 데이터 재준비** | `prepare_sft_data.py` 재실행 (강화 필터 적용) | **20-30분** | 30분 |
| **B. 1B SFT 재학습** | 7500 steps, 8×B200, dynamic padding 적용 | **30-40분** | 1시간 |
| **C. 1B 평가** | 반복률 + 생성 품질 + (선택) ko_ifeval | **30분-2시간** | 1.5-3시간 |
| **D. 3B pretrain** | 150B tokens, 8×B200 | **~26시간** | 27-29시간 |
| **E. 3B SFT** | 동일 데이터, 10000 steps | **1.5-2시간** | 29-31시간 |
| **F. 3B 평가** | 전체 벤치마크 | **2-4시간** | 31-35시간 |

### 5.2 현재 고쳐서 가는 시간 vs 재시작

| 경로 | 소요 시간 | 예상 최종 품질 |
|------|----------|---------------|
| **경로 A: 현재 모델에서 추가 학습** | 추가 SFT 40분 + 평가 2시간 = ~3시간 | 반복률 12-15%, 잔여 오염 |
| **경로 B: 1B 클린 재학습** | 데이터 30분 + SFT 40분 + 평가 2시간 = **~3시간** | 반복률 **5-8%**, 오염 없음 |
| **경로 C: 3B 처음부터** | 데이터 30분 + pretrain 26시간 + SFT 2시간 + 평가 4시간 = **~33시간** | 반복률 **2-5%**, ko_ifeval 35-45% |

**경로 A와 B의 시간이 거의 같은데, B가 품질이 확실히 높다.** 이것이 재시작을 권장하는 핵심 이유다.

---

## 6. 재시작의 리스크와 예방

### 6.1 "다시 해도 또 새로운 문제가 나올 수 있다"

| 리스크 | 확률 | 예방 방법 |
|--------|------|----------|
| 데이터 파이프라인 새 버그 | 낮음 | 코드 이미 수정/검증됨, 단위 테스트 추가 |
| 과적합 감지 실패 | 낮음 | val split 이번엔 반드시 사용 |
| 새로운 유형의 반복 | 중간 | 다양한 프롬프트로 평가, rep_penalty 보험 |
| 학습 불안정 (loss spike) | 낮음 | 기존 학습에서 안정적이었음, 동일 lr 사용 |
| 데이터 필터 과도 → 데이터 부족 | 낮음 | 120K 여전히 충분 (3-4 epoch에 적합) |

### 6.2 지금까지의 교훈 반영 체크리스트

```
✅ 추론 시 올바른 프롬프트 포맷 (<|user|>/<|assistant|>) 사용
✅ Dynamic padding 실제 작동 확인 (배치별 가변 길이)
✅ 트렁케이션 시 EOS 강제 삽입
✅ EOS 리터럴 / Q&A 마커 오염 데이터 필터링
✅ 가중치 샘플링 정상화 (5.0 → 2.0)
✅ Val split으로 과적합 모니터링
✅ 3-4 epoch 충분히 학습
✅ 평가 시 rep_penalty=1.1 + no_repeat_ngram=3 기본 적용
✅ 다양한 프롬프트 유형으로 종합 평가
```

### 6.3 성공 확률 추정

- **위 체크리스트 100% 반영 시**: 반복률 5-8% 달성 확률 **85-90%**
- **기존 대비 개선**: 반복률 17.7% → 5-8% (55-70% 감소)
- **실패 시나리오**: 반복률이 10-15%에 머무는 경우 → 추가 대응 (ORPO/DPO)

---

## 7. 최종 결론 및 권장

### 7.1 "다시 시작"이 필요한 근본적 이유

**필요하다.** 이유:

1. **비용이 거의 없다** — 1B SFT 재학습은 40분. 기존 모델에서 추가 학습하는 시간과 동일.
2. **오염된 가중치 위에 쌓는 것은 비효율적** — OpenOrca 5배 업샘플링 + Q/A 마커 오염의 흔적이 남아있는 상태에서 추가 학습하면, 새 gradient가 오래된 오염을 완전히 덮지 못함.
3. **모든 수정 사항이 이미 코드에 반영됨** — sft_dataset.py (dynamic padding, EOS 보장), prepare_sft_data.py (강화 필터, 가중치 수정) 모두 수정 완료. 실행만 하면 됨.
4. **깨끗한 기준선이 필요** — 3B로 스케일업하기 전에, 깨끗한 1B 결과가 있어야 파이프라인이 올바른지 검증 가능.

### 7.2 다시 시작 시 예상 최종 품질

| 지표 | 현재 (수정 추론) | 1B 재학습 예상 | 3B 재학습 예상 |
|------|-----------------|---------------|---------------|
| 반복률 (3-gram) | 17.7% | **5-8%** | **2-5%** |
| 반복률 (rep_penalty 없이) | ~30% | **10-15%** | **5-10%** |
| EOS 정상 종료율 | ~60% | **85-90%** | **90-95%** |
| ko_ifeval (추정) | 15-25% | **20-30%** | **35-45%** |
| ko_winogrande (추정) | 50-55% | **53-58%** | **60-68%** |
| 한국어 답변 자연스러움 | 중간 | **중상** | **상** |

### 7.3 타임라인

```
[오늘 — 3시간]
├── 데이터 재준비: prepare_sft_data.py 재실행 (30분)
├── 1B SFT 재학습: 7500 steps (40분)
└── 평가: 반복률 + 생성 품질 (30분-2시간)

[내일-모레 — 30시간]
├── 3B pretrain (26시간, 백그라운드)
├── 3B SFT (2시간)
└── 3B 전체 평가 (2-4시간)
```

### 7.4 최종 권장

| 권장 | 근거 |
|------|------|
| ✅ **1B SFT 즉시 재학습** | 40분 투자, 반복률 17.7% → 5-8% 예상, 리스크 극히 낮음 |
| ✅ **3B pretrain 병렬 시작** | 1B 재학습 결과로 파이프라인 검증 후 동일 파이프라인 적용 |
| ❌ **현재 가중치에서 추가 학습** | 같은 시간으로 더 낮은 품질. 오염 잔류 위험. |

**한 줄 요약: 40분이면 깨끗한 모델을 얻을 수 있는데, 오염된 모델에 40분을 더 쓸 이유가 없다.**

---

## 부록: 재학습 실행 명령어

```bash
# Step 1: 데이터 재준비 (강화 필터 + 수정된 가중치 적용)
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
python data/prepare_sft_data.py --output_dir data/sft_v2/ --val_split 0.05

# Step 2: 1B SFT 재학습
torchrun --nproc_per_node=8 train/sft.py \
    --base_checkpoint checkpoints/korean_1b_fp8_run1/checkpoint-0034000 \
    --sft_data data/sft_v2/train.jsonl \
    --val_data data/sft_v2/val.jsonl \
    --checkpoint_dir checkpoints/korean_1b_sft_v2 \
    --max_steps 7500 \
    --batch_size 4 \
    --grad_accum 2 \
    --lr 2e-5 \
    --warmup_steps 225 \
    --use_fp8

# Step 3: 평가
python eval/test_generation_params.py \
    --checkpoint checkpoints/korean_1b_sft_v2/checkpoint-0007500
```
