# "처음부터 다시 시작" 시나리오 완전 분석 **작성일**: 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 | `` 리터럴 오염 데이터 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건)의 패턴도 가중치에 분산 저장 - `` 리터럴이 포함된 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 ```