Files
frankenstallm/source/eval/decision/FINAL_DECISION_REPORT.md
ModelHub XC d4abdb70fa 初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm
Source: Original Platform
2026-07-14 04:21:16 +08:00

337 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SFT 품질 위기 분석 및 의사결정 보고서
**작성일:** 2026-02-26
**작성자:** Optimus Prime (AI)
**판결 유형:** 중립적 판사 — 모든 보고서 종합 후 최종 결론
---
## 1. 현재 상황 요약
| 항목 | 값 |
|------|-----|
| 모델 | Korean 1B SFT (1.19B params) |
| 학습 | 5,000 steps, ~39분, 8× B200 |
| Final Loss | 1.9677 (수렴 근접, 아직 미세 하강 중) |
| 반복률 (잘못된 포맷) | 57% → **근본 원인: 프롬프트 포맷 불일치** |
| 반복률 (올바른 포맷) | 30.7% → +rep_penalty 적용 시 **17.7%** |
| 반복률 (올바른 포맷 + rep_penalty=1.1만) | **~5%** (실험 결과) |
| 반복률 (올바른 포맷 + rep_penalty=1.1 + no_repeat_3gram) | **0.0%** |
| SFT 데이터 | 159,125 샘플, ~2 epochs |
| Epoch 수 | ~2 (업계 표준 3-5 대비 부족) |
**핵심 사실:** 원래 보고된 57% 반복률의 대부분은 **추론 시 프롬프트 포맷 불일치** 때문이었다. 학습은 `<|user|>/<|assistant|>` 포맷인데 평가는 `### 질문:/### 답변:` 포맷으로 수행됨. 이 포맷만 맞추면 57% → 5%로 급감하고, rep_penalty=1.1 추가 시 0%까지 도달.
---
## 2. 발견된 문제들 전체 목록
### 🔴 Critical (학습 품질에 직접 영향)
| # | 문제 | 심각도 | 상태 |
|---|------|--------|------|
| 1 | **추론 프롬프트 포맷 불일치** (학습≠평가) | 🔴 Critical | ✅ 수정됨 |
| 2 | **Static Padding** — Dynamic padding이 사실상 무효화 (4096 고정) | 🔴 Critical | ❌ 미수정 |
| 3 | **트렁케이션 시 EOS 손실** — 잘린 샘플에서 EOS 미학습 | 🔴 Critical | ❌ 미수정 (0.04%만 해당) |
| 4 | **Epoch 부족** — ~2 epochs (업계 표준 3-5) | 🔴 Critical | ❌ 미수정 |
| 5 | **Validation split 없음** — 과적합 모니터링 불가 | 🔴 Critical | ❌ 미수정 |
### 🟡 Important (데이터 품질)
| # | 문제 | 영향 |
|---|------|------|
| 6 | Output 내 `</s>` 리터럴 113건 | EOS 학습 혼란 |
| 7 | Output 내 Q/A 마커 ~550건 | 자체 Q/A 루프 패턴 학습 |
| 8 | 자체 반복 패턴 57건 | 반복 생성 직접 학습 |
| 9 | 짧은 output (<50자) 16,519건 (10.4%) | EOS 타이밍 불안정 |
| 10 | OpenOrca 5배 업샘플링 | 과적합 위험, 다양성 부족 |
| 11 | `<\|user\|>/<\|assistant\|>` 특수토큰 미등록 | 서브워드 분할 (경미) |
### 🟢 Minor
| # | 문제 | 영향 |
|---|------|------|
| 12 | 한국어 비율 30% 미만 샘플 13.7% | 일관성 저하 |
| 13 | Label shift 마지막 position 미학습 | EOS 이후 생성 경향 |
---
## 3. 고쳐서 가는 시나리오 (Fix & Continue)
### 시나리오 상세
현재 checkpoint-5000 위에서 추가 학습 (resume 또는 lr=1e-5로 continuation):
| 단계 | 작업 | 소요 시간 |
|------|------|-----------|
| 1 | 데이터 필터링 (품질 문제 샘플 제거) | 30분 |
| 2 | Val split 생성 | 10분 |
| 3 | 추가 학습 5,000 steps (lr=1e-5, epoch 3-4) | ~40분 |
| 4 | 평가 | 30분 |
| **합계** | | **~2시간** |
### 예상 개선 효과
| 지표 | 현재 | 예상 |
|------|------|------|
| Loss | 1.97 | 1.90-1.93 |
| 반복률 (올바른 포맷 + rep_penalty) | 17.7% | 10-15% |
| ko_ifeval | 미측정 (15-28% 추정) | +3-7%p |
### 리스크
- **Static padding 미수정**: 학습 속도 3-8x 낭비 지속 40분이면 괜찮지만 비효율
- **오염된 가중치 위에 쌓기**: EOS 경계 혼란 + 반복 패턴이 이미 가중치에 학습됨 추가 학습으로 완전히 "잊을" 있는가 불확실
- **cosine schedule 문제**: 기존 5000 steps 기준으로 LR이 이미 2e-6까지 decay resume LR 재설정 필요
- 🟡 **천장 효과**: 오염된 가중치의 한계가 어디인지 모름
---
## 4. 처음부터 다시 시나리오 (Restart from Base)
### 시나리오 상세
base checkpoint (pretrained korean_1b_fp8_run1/checkpoint-0034000)에서 깨끗한 데이터로 SFT 재시작:
| 단계 | 작업 | 소요 시간 |
|------|------|-----------|
| 1 | 데이터 필터링 (159K ~120-130K) | 30분 |
| 2 | sft_dataset.py 수정 (dynamic padding 실제 작동, EOS 보존) | 30분 |
| 3 | Val split 생성 | 10분 |
| 4 | launch_sft.sh 수정 (10,000 steps, val_data, 가중치 조정) | 10분 |
| 5 | 학습 실행 (10,000 steps, dynamic padding 적용 기존보다 빠를 있음) | ~40-80분 |
| 6 | 평가 | 30분 |
| **합계** | | **~2.5-3시간** |
### 예상 품질
| 지표 | 예상 |
|------|------|
| Loss | 1.85-1.92 |
| 반복률 (올바른 포맷, rep_penalty=1.1) | **<5%** |
| ko_ifeval | 20-30% (1B 한계 최적) |
### 리스크
- 🟢 **리스크 낮음**: 이미 데이터/코드가 모두 준비되어 있음
- 🟢 **결과 예측 가능**: 깨끗한 데이터 + 올바른 패딩 + 충분한 epoch 표준적 결과 기대
- **유일한 리스크**: 코드 수정(sft_dataset.py) 새로운 버그 도입 가능성 작은 subset으로 sanity check 필요
---
## 5. 최종 판결 및 근거
### 판결: 🟢 **처음부터 다시 (Restart)** — 즉시 재학습
### 핵심 논거
#### 1. 17.7% 반복률은 "고쳐야 할 수준"인가?
**결론: 배포 불가, 그러나 위기는 아니다.**
- 17.7% rep_penalty + no_repeat_3gram 적용 수치. 기법 없이는 30.7%
- 상업적 서비스 기준: 반복률 <5% 업계 표준. 17.7% 사용자 10명 2명이 반복 문장을 목격
- **그러나** 올바른 포맷 + rep_penalty=1.1만으로 이미 ~5% 달성 모델 자체는 나쁘지 않음
- 진짜 문제는 반복률보다 **코드/데이터 파이프라인의 다수 미수정 버그**
#### 2. 현재 가중치는 구제 가능한가?
**결론: 구제 가능하나, 비용 대비 비효율적.**
- EOS truncation은 0.04% 해당 가중치 오염 경미
- Static padding은 가중치 품질에는 영향 없음 (학습 속도만 낭비)
- 데이터 품질 문제 (</s> 리터럴, Q/A 마커, 짧은 output)는 가중치에 이미 학습됨
- 추가 학습으로 "잊기"는 가능하지만, 깨끗하게 다시 학습하는 것과 시간 차이가 크지 않음
#### 3. 재시작 비용은?
**결론: 매우 낮음. Fix 대비 추가 비용 ~1시간.**
| | Fix (Continue) | Restart |
|---|---|---|
| 데이터 준비 | 30분 | 30분 (동일) |
| 코드 수정 | 0분 | 40분 (sft_dataset.py) |
| 학습 | 40분 | 40-80분 |
| 평가 | 30분 | 30분 (동일) |
| **합계** | **~2시간** | **~2.5-3시간** |
| **결과 품질** | 개선되지만 한계 있음 | **깨끗한 최적 결과** |
**추가 비용 1시간으로 깨끗한 기반을 확보**할 수 있다. 이 1시간은 이후 3B 전환, ORPO/DPO 적용 시 "오염된 가중치에서 시작해야 하나?"라는 고민을 완전히 제거한다.
#### 4. 어느 경로가 목표 달성이 빠른가?
**목표: 반복률 <5%, ko_ifeval 25%**
- **Fix 경로**: 17.7% → 추가 학습 → 10-15% → 여전히 >5%. ORPO 추가 필요 → +6시간. 총 ~8시간
- **Restart 경로**: 깨끗한 재학습 → <5% (추론 파라미터 포함) + ko_ifeval 20-30%. ~3시간
- **Restart가 2.5배 빠름**
### 결정적 수치 근거
```
재학습 추가 비용: +1시간 (Fix 대비)
반복률 예상 개선: 17.7% → <5% (3.5배 개선)
미수정 버그 해소: 5개 → 0개 (static padding, EOS 보존, epoch, val split, 데이터 필터)
향후 3B/ORPO 기반: 오염 가중치 → 깨끗한 가중치
ROI: 1시간 투자 → 모든 기술 부채 청산
```
---
## 6. 실행 계획 (구체적 Next Steps)
### Step 1: 데이터 필터링 (30분)
```bash
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
python eval/data_quality_audit.py # 또는 enhanced_quality_filter.py 실행
# 159K → ~120-130K 예상
```
**수행 내용:**
- `</s>`, `<|endoftext|>`, `EOS` 리터럴 포함 샘플 제거 (161건)
- Q/A 마커 포함 샘플 제거 (~550건)
- Output <80자 샘플 제거 (~16K건)
- N-gram 반복 샘플 제거 (57건)
- 한국어 비율 <40% 샘플 제거
**성공 기준:** 필터링 120K-135K 샘플 남음. 제거된 샘플 spot check 실제 저품질 확인.
### Step 2: 코드 수정 (40분)
**2-1. sft_dataset.py — Dynamic padding 실제 작동** (가장 중요)
- `__getitem__`에서 고정 4096 패딩 제거
- 실제 길이 텐서만 반환
- `dynamic_collate_fn` 배치별 패딩 수행
**2-2. sft_dataset.py — EOS 보존**
```python
response_ids = response_ids[:allowed_response - 1] + [self.eos_token_id]
```
**2-3. 데이터 가중치 조정**
- OpenOrca: 5.0 2.0
- kovast: 0.8 0.5
**성공 기준:** 수정 작은 subset (1000 샘플, 100 steps)으로 학습이 정상 실행되는지 확인. Loss가 합리적 범위 (2.0-2.5)에서 시작.
### Step 3: Val Split + Config 수정 (10분)
```bash
# 90/10 split
python -c "
import json, random
random.seed(42)
with open('data/sft/train_cleaned.jsonl') as f:
lines = f.readlines()
random.shuffle(lines)
split = int(len(lines) * 0.9)
with open('data/sft/train_split.jsonl', 'w') as f:
f.writelines(lines[:split])
with open('data/sft/val_split.jsonl', 'w') as f:
f.writelines(lines[split:])
"
```
**launch_sft.sh 수정:**
- `--max_steps 10000` (3-4 epochs)
- `--val_data data/sft/val_split.jsonl`
- `--lr 2e-5` (초기 학습이므로 유지)
- `--warmup_steps 300`
**성공 기준:** Config 파일 변경 확인, val split 크기 ~12-13K 확인.
### Step 4: 재학습 실행 (~40-80분)
```bash
bash scripts/launch_sft.sh
```
**모니터링:**
- Loss curve: 지속적 하강 확인
- Val loss: 500 steps 체크, 상승 early stop
- GNorm: 1.5 미만 유지
**성공 기준:**
- Train loss < 1.90
- Val loss가 train loss의 1.1배 이내 (과적합 없음)
- 학습 속도: dynamic padding으로 기존 대비 2x+ 향상 확인
### Step 5: 평가 (30분)
```bash
# 1. 반복률 측정 (올바른 포맷)
python eval/test_generation_params.py # 수정된 포맷
# 2. 다양한 rep_penalty에서 반복률
# rep_penalty=1.0 (없음): 목표 <10%
# rep_penalty=1.1: 목표 <3%
# 3. ko_ifeval (가능하면)
lm_eval --model hf --tasks ko_ifeval ...
```
**성공 기준:**
| 지표 | 목표 | 실패 기준 |
|------|------|-----------|
| 반복률 (rep_penalty 없이) | <10% | >20% |
| 반복률 (rep_penalty=1.1) | <3% | >10% |
| Train loss | <1.90 | >2.00 |
| ko_ifeval | >20% | <15% |
### Step 6 (Optional): 3B 전환 준비
재학습 성공 , 동일한 깨끗한 파이프라인으로 3B pretrain SFT 진행 가능.
재학습 실패 , 문제 원인 분석 데이터/아키텍처 수준에서 재검토.
---
## 7. 성공 기준 (각 단계별 체크포인트)
```
Step 1 ✅ 데이터 필터링
□ 120K-135K 샘플 남음
□ 제거된 샘플이 실제 저품질임을 spot check
Step 2 ✅ 코드 수정
□ 100 steps sanity check 통과
□ 배치 내 시퀀스 길이가 가변적 (4096 고정 아님)
□ 트렁케이션 샘플에서 마지막 토큰이 EOS
Step 3 ✅ Config
□ Val split ~12-13K 샘플
□ max_steps=10000, val_data 경로 설정
Step 4 ✅ 학습
□ Train loss < 1.90
□ Val loss ≤ Train loss × 1.1
□ 학습 속도 ≥ 2x 기존 대비 (dynamic padding 효과)
Step 5 ✅ 평가
□ 반복률 < 10% (rep_penalty 없이)
□ 반복률 < 3% (rep_penalty=1.1)
□ ko_ifeval > 20%
최종 ✅ 목표 달성
□ 반복률 < 5% (실용적 설정)
□ ko_ifeval > 25% (1B 한계 내 최적)
□ 깨끗한 가중치 → 3B/ORPO 기반으로 사용 가능
```
---
## 부록: 왜 "제3의 선택지"는 아닌가
**"1B 고쳐서 재학습 바로 3B 전환"** 옵션도 고려했으나:
- 1B 재학습 자체가 3시간이면 끝남 별도 "고쳐서" 단계가 필요 없음
- 3B 전환은 1B 결과와 무관하게 진행 가능 (sft_dataset.py 수정은 3B에도 그대로 적용)
- 따라서 "깨끗하게 재학습" = "3B 전환 준비" 자연스럽게 포함됨
**결론: Restart가 Fix의 상위 호환이다.** Fix로 있는 모든 것을 Restart가 포함하면서, 추가로 코드 버그까지 수정한다. 비용 차이는 1시간.
---
*"40분 아끼려고 기술 부채를 안고 가지 마라. 3시간 투자해서 깨끗한 기반을 만들어라."*