18 KiB
18 KiB
🗺️ 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분)
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자) 제거 확인
# 결과 확인
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 |
☐ 배치별 가변 패딩 |
# 핵심 코드 확인
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분)
# 변경할 값들:
# 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분)
# 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분)
실행 명령어
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
모니터링
실시간 로그:
tail -f checkpoints/korean_1b_sft_v2/train.log
TensorBoard:
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분)
# 올바른 포맷(<|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분)
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시간)
# 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시간)
# 한국어 preference 데이터 다운로드
python -c "
from datasets import load_dataset
# 옵션 1: ko_Ultrafeedback (60K, 일반 도메인)
ds = load_dataset('maywell/ko_Ultrafeedback')
# 옵션 2: 자체 생성 (현재 모델로 rejected 생성)
"
자체 생성 방법:
- 현재 SFT 모델로 동일 프롬프트에 여러 번 생성
- 반복/저품질 출력 → rejected
- 깨끗한 데이터의 정답 → chosen
- ~10K-20K 쌍 생성
ORPO 학습 (1.5시간)
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시간)
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시간)
# 증가된 데이터로 재학습
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 |
설정 파일 작성
# 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 최소 충족 |
데이터 준비 명령어:
# 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
학습 실행
# 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시간
모니터링
# 로그 확인
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|> |
실행
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시간)
# 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시간)
# 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시간)
# 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 최적):
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 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%, 서빙 정상 | 모델 개선 후 재배포 |
🔥 오늘 당장 시작할 첫 번째 명령어
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 완료 시 결과로 업데이트할 것.