์ด๋ฒค์ ธ์ค ํ 2๋ฒ โ ORPO + ๊ณ ํ์ง ๋ฐ์ดํฐ๋ก 1B ์์ฑ ์ ๋ต
์์ฑ์ผ: 2026-02-27
์ ๋ต: ํ์ฌ 1B SFT v2 ๋ชจ๋ธ์ ORPO๋ก ๋ฐ๋ณต๋ฅ <5% ๋ฌ์ฑ
ํ์ฌ ์ํ: ๋ฐ๋ณต๋ฅ 18.0%, val_loss 2.2062
1. ๋ฐ๋ณต๋ฅ 18% โ <5% ๋ฌ์ฑ ๋ก๋๋งต
Step A: ์ถ๋ก ํ๋ผ๋ฏธํฐ ํ๋ (์ฆ์, 0์๊ฐ)
| ํ๋ผ๋ฏธํฐ | ํ์ฌ | ๋ณ๊ฒฝ |
|---|---|---|
| repetition_penalty | 1.1 | 1.2 |
| no_repeat_ngram_size | 3 | 4 |
์์ ๋ฐ๋ณต๋ฅ : 18% โ 10~12%
- ๊ทผ๊ฑฐ: ํ์ฌ eval์์ repetition_penalty=1.1๋ก ์ธก์ . 1.2๋ก ์ฌ๋ฆฌ๋ฉด n-gram ๋ฐ๋ณต์ด ์ง์ ์ต์ ๋จ
- ํ๊ณ: ์์ฑ ํ์ง ์ ํ ์์ด ๊ฐ๋ฅํ ๋ฒ์. 1.3 ์ด์์ ๋ฌธ๋งฅ coherence ์์
- ๋ ๋ฆฝ ํจ๊ณผ: ๋ชจ๋ธ ๊ฐ์ค์น ๋ณ๊ฒฝ ์์ด ์ฆ์ ์ ์ฉ. ๋ค๋ฅธ ๋จ๊ณ์ ์์ ํ ๋ ๋ฆฝ
Step B: ORPO ํ์ต (ํต์ฌ, 3~5์๊ฐ)
์์ ๋ฐ๋ณต๋ฅ : 1012% โ 47%
ORPO(Odds Ratio Preference Optimization)๋ SFT + preference alignment๋ฅผ ๋จ์ผ ๋ชฉ์ ํจ์๋ก ํตํฉ:
- SFT loss๋ก chosen ์๋ต ํ์ต
- Odds ratio๋ก chosen vs rejected ์ ํธ๋ ํ์ต
- DPO ๋๋น reference model ๋ถํ์ โ ๋ฉ๋ชจ๋ฆฌ/์๊ฐ ์ ์ฝ
์ ORPO๊ฐ ๋ฐ๋ณต ํดํ์ ํจ๊ณผ์ ์ธ๊ฐ:
- ๋ฐ๋ณต ์๋ต์ rejected๋ก ๋ช ์์ ํ์ต โ ๋ชจ๋ธ์ด "๋ฐ๋ณตํ์ง ๋ง๋ผ"๋ฅผ ์ง์ ๋ฐฐ์
- SFT๋ง์ผ๋ก๋ "๋ญ ํ๋ฉด ์ ๋๋์ง" ํ์ต ๋ถ๊ฐ โ preference learning์ด ์ ์ผํ ํด๋ฒ
- 1B ๋ชจ๋ธ์ ๋ฐ๋ณต์ ํ๋ผ๋ฏธํฐ ๋ถ์กฑ์ด ์๋ EOS ๊ฒฝ๊ณ ํ์ต ์คํจ + ๋ฐ๋ณต ํจํด ๋ฏธ๋ฒ์น โ ORPO๋ก ์ง์ ๊ต์ ๊ฐ๋ฅ
ํ์ ๋ฐ์ดํฐ: 500~2000 preference ์ (์๋ ์น์ 2 ์ฐธ์กฐ)
Step C: ๋ฐ์ดํฐ ์ ์ + ์ถ๊ฐ SFT (์ ํ์ , 2~4์๊ฐ)
์์ ๋ฐ๋ณต๋ฅ : 47% โ 35%
- data_quality_audit์์ ๋ฐ๊ฒฌ๋ ๋ฌธ์ ์์ :
</s>์ค์ผ 113๊ฑด ์ ๊ฑฐ- ์งง์ output(<80์) 16,519๊ฑด ์ ๊ฑฐ
- Q/A ๋ง์ปค ~550๊ฑด ์ ๊ฑฐ
- OpenOrca ๊ฐ์ค์น 5.0โ2.0
- ์ ์ ๋ ~120K ๋ฐ์ดํฐ๋ก ์ถ๊ฐ SFT 2-3 epochs
๋ ๋ฆฝ ํจ๊ณผ: ๋ฐ์ดํฐ ํ์ง ๊ฐ์ ์ ORPO์ ๋ฌด๊ดํ๊ฒ ๊ธฐ์ ๋ชจ๋ธ ๊ฐ์ . ํ์ง๋ง ORPO ์์ด ์ด๊ฒ๋ง์ผ๋ก๋ ๋ฐ๋ณต๋ฅ <5% ๋ถ๊ฐ๋ฅ (SFT v1โv2์์ ์ด๋ฏธ ๋ฐ์ดํฐ ์ ์ ํ์ผ๋ 17.7%โ18%๋ก ์ ์ฒด)
์ข ํฉ ์์
| ๋จ๊ณ | ๋ฐ๋ณต๋ฅ | ์์์๊ฐ | ๋์ ์๊ฐ |
|---|---|---|---|
| ํ์ฌ | 18.0% | - | - |
| Step A (์ถ๋ก ํ๋ผ๋ฏธํฐ) | 10~12% | 0h | 0h |
| Step B (ORPO) | 4~7% | 3~5h | 3~5h |
| Step C (๋ฐ์ดํฐ ์ ์ SFT) | 3~5% | 2~4h | 5~9h |
| ์ต์ข | 3~5% | 5~9h |
2. ์์ฒด Preference ๋ฐ์ดํฐ ์์ฑ ์ ๋ต
๋ฐฉ๋ฒ: Self-Play Rejection Sampling
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model = AutoModelForCausalLM.from_pretrained("checkpoints/korean_1b_sft/checkpoint-best")
tokenizer = AutoTokenizer.from_pretrained(...)
def generate_preference_pair(prompt, n_samples=8, temp=0.9):
"""ํ๋กฌํํธ ๋น n_samples๊ฐ ์์ฑ โ chosen/rejected ๋ถ๋ฅ"""
responses = []
for _ in range(n_samples):
output = model.generate(
tokenizer.encode(f"<|user|>\n{prompt}\n<|assistant|>\n", return_tensors="pt"),
max_new_tokens=256, temperature=temp, top_p=0.95,
do_sample=True, repetition_penalty=1.0 # ์๋์ ์ผ๋ก penalty ์์ด
)
text = tokenizer.decode(output[0], skip_special_tokens=True)
rep_rate = calc_repetition_rate(text) # 10-gram ๊ธฐ์ค
responses.append((text, rep_rate))
# ๋ถ๋ฅ
chosen = [r for r in responses if r[1] < 0.05] # ๋ฐ๋ณต๋ฅ 5% ๋ฏธ๋ง โ chosen
rejected = [r for r in responses if r[1] > 0.15] # ๋ฐ๋ณต๋ฅ 15% ์ด์ โ rejected
if chosen and rejected:
return {"prompt": prompt, "chosen": chosen[0][0], "rejected": rejected[0][0]}
return None
๊ท๋ชจ ๊ณ์ฐ
| ํญ๋ชฉ | ๊ฐ |
|---|---|
| ํ์ preference ์ | 500~1000 (์ต์ 500) |
| ํ๋กฌํํธ ๋น ์ํ ์ | 8 |
| ์ ํจ ์ ์์ฑ๋ฅ | ~40% (๋ฐ๋ณต๋ฅ 18%์ด๋ฏ๋ก chosen/rejected ๋ถ๋ฆฌ ๊ฐ๋ฅ) |
| ํ์ ํ๋กฌํํธ ์ | 500 / 0.4 = ~1,250๊ฐ |
| ํ๋กฌํํธ ๋น ์์ฑ ์๊ฐ | 8 ร 256 tokens ร ~0.02s/token โ 40s |
| ์ด ์์ฑ ์๊ฐ | 1,250 ร 40s โ 14์๊ฐ (GPU 1๊ฐ) |
โ ๏ธ ์์ฒด ์์ฑ์ ๋๋ฆผ. ๋์: ๊ธฐ์กด HF preference ๋ฐ์ดํฐ ํ์ฉ (์น์ 3)
์๋ ํ์ง ํ๋จ ๊ธฐ์ค
- chosen ์๊ณ๊ฐ: 10-gram ๋ฐ๋ณต๋ฅ < 5%, ๊ธธ์ด > 50 tokens, EOS ์ ์ ์์ฑ
- rejected ์๊ณ๊ฐ: 10-gram ๋ฐ๋ณต๋ฅ > 15% OR ๋์ผ ๋ฌธ์ฅ 2ํ ์ด์ ๋ฐ๋ณต
- ์ค๊ฐ ์์ญ(5~15%)์ ๋ฒ๋ฆผ โ contrastive signal ๊ทน๋ํ
๋น ๋ฅธ ๋์: ํ์ด๋ธ๋ฆฌ๋ ์ ๋ต (์ถ์ฒ)
- HF์์ 500~1000์ ๋ค์ด๋ก๋ (์ฆ์)
- ์์ฒด ๋ชจ๋ธ๋ก 200
300์ ์ถ๊ฐ ์์ฑ (๋ฐ๋ณต ํนํ, 34์๊ฐ) - ์ด 700~1300์์ผ๋ก ORPO ํ์ต
3. HuggingFace ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅ ํ๊ตญ์ด Preference ๋ฐ์ดํฐ
ํ์ธ๋ ๋ฐ์ดํฐ์
| ๋ฐ์ดํฐ์ | ํฌ๊ธฐ | ํฌ๋งท | ์ ํฉ์ฑ |
|---|---|---|---|
maywell/ko_Ultrafeedback_binarized |
61,966์ | prompt/chosen/rejected | โญโญโญ ์ต์ โ ๋ฐ๋ก ORPO์ ์ฌ์ฉ ๊ฐ๋ฅ |
kuotient/orca-math-korean-dpo-pairs |
192,848์ | question/chosen/rejected | โญโญ ์ํ ํนํ์ง๋ง ์ ํ๋ถ |
nayohan/preference-collection-ko-full |
199,760์ | ๋ณต์ก ํฌ๋งท (score_A/B) | โญโญ ์ ์ฒ๋ฆฌ ํ์ |
jojo0217/korean_rlhf_dataset |
๋ฏธํ์ธ | ๋ฏธํ์ธ | โญ ํ์ธ ํ์ |
heegyu/PKU-SafeRLHF-ko |
๋ฏธํ์ธ | ๋ฏธํ์ธ | โญ ์์ ์ฑ ํนํ |
์ถ์ฒ ์กฐํฉ
# 1์์: ko_Ultrafeedback_binarized์์ 2000์ ์ํ๋ง
from datasets import load_dataset
ds = load_dataset("maywell/ko_Ultrafeedback_binarized", split="train")
# ์ด๋ฏธ prompt/chosen/rejected ํฌ๋งท โ ๋ฐ๋ก ์ฌ์ฉ
# 2์์: orca-math์์ 500์ ์ถ๊ฐ (๋ค์์ฑ)
ds2 = load_dataset("kuotient/orca-math-korean-dpo-pairs", split="train")
์ค๋น ์๊ฐ: 30๋ถ ๋ฏธ๋ง (๋ค์ด๋ก๋ + ํฌ๋งท ๋ณํ)
4. 1B ๋ชจ๋ธ์ ํ๊ณ์ ORPO ๊ทน๋ณต ๋ฒ์
๋ฐ๋ณต ํดํ์ ๊ทผ๋ณธ ์์ธ: ํ๋ผ๋ฏธํฐ ์ vs ํ์ต ๋ฐฉ๋ฒ
ํ๋ผ๋ฏธํฐ ์๊ฐ ์ฃผ ์์ธ์ด ์๋ ๊ทผ๊ฑฐ:
- Pretrain ๋จ๊ณ์์ ๋ฐ๋ณต๋ฅ 69% โ SFT๋ก 18%๊น์ง ๋ฎ์ถค. ๊ฐ์ 1B ํ๋ผ๋ฏธํฐ๋ก 51%p ๊ฐ์
- ๋ฐ๋ณต ํจํด์ ํน์ ํ๋กฌํํธ์์๋ง ๋ฐ์ (์งง์ ์ฌ์ค ์ง๋ฌธ์ 0%, ๊ธด ์ค๋ช ์ง๋ฌธ์์ 20~33%)
- data_quality_audit์์ EOS ํ์ต ์คํจ๊ฐ ํต์ฌ ์์ธ์ผ๋ก ์ง๋ชฉ๋จ โ ํ์ต ๋ฐ์ดํฐ/๋ฐฉ๋ฒ ๋ฌธ์
1B์์ ๋ฐ๋ณต๋ฅ <5% ํ์ค์ฑ:
- Qwen2.5-0.5B, SmolLM-1.7B ๋ฑ ์ ์ฌ ๊ท๋ชจ ๋ชจ๋ธ์ด RLHF/DPO ํ ๋ฐ๋ณต๋ฅ <5% ๋ฌ์ฑ ์ฌ๋ก ๋ค์
- ORPO ์๋ ผ๋ฌธ(Hong et al., 2024)์์ Phi-2(2.7B)์ Llama-2-7B ์คํ โ ์๊ท๋ชจ ๋ชจ๋ธ์์๋ ์ผ๊ด๋ ๊ฐ์
- 1B๊ธ ์ง์ ์คํ์ ๋๋ฌผ์ง๋ง, ๋ฐ๋ณต ํดํ๋ alignment ๋ฌธ์ ์ด์ง capacity ๋ฌธ์ ๊ฐ ์๋
ORPO ํน์ ์ ์ฅ์ (1B์ ์ ๋ฆฌ):
- Reference model ๋ถํ์ โ GPU ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ (DPO๋ 2๋ฐฐ ๋ฉ๋ชจ๋ฆฌ)
- 1B ๋ชจ๋ธ์ ๋จ์ผ GPU์์ full fine-tuning ๊ฐ๋ฅ
- SFT + preference๋ฅผ ๋์์ ํ์ต โ ์ ์ ๋ฐ์ดํฐ๋ก ํจ์จ์
ํ์ค์ ๊ธฐ๋์น
| ๋ชฉํ | ๋ฌ์ฑ ๊ฐ๋ฅ์ฑ | ์กฐ๊ฑด |
|---|---|---|
| ๋ฐ๋ณต๋ฅ <10% | 95% | ORPO 500์ + rep_penalty=1.2 |
| ๋ฐ๋ณต๋ฅ <5% | 70% | ORPO 1000์ + ๋ฐ์ดํฐ ์ ์ SFT |
| ๋ฐ๋ณต๋ฅ <3% | 40% | ORPO 2000์ + ๋ฐ์ดํฐ ์ ์ + ํ๋ผ๋ฏธํฐ ํ๋ |
5. ์ด ๋น์ฉ ๊ณ์ฐ
1B ORPO ๊ฒฝ๋ก (์ด ์ ๋ต)
| ๋จ๊ณ | ์์ | ์๊ฐ |
|---|---|---|
| 1 | HF preference ๋ฐ์ดํฐ ๋ค์ด๋ก๋ + ์ ์ฒ๋ฆฌ | 0.5h |
| 2 | ์์ฒด preference ์์ฑ (200~300์, ์ ํ์ ) | 3~4h |
| 3 | ORPO ํ์ต (1000์, 1~2 epochs) | 1~2h |
| 4 | ํ๊ฐ + ๋ฐ๋ณต | 0.5h |
| 5 | (์ ํ) ๋ฐ์ดํฐ ์ ์ ์ฌSFT | 2~4h |
| ์ดํฉ (ํ์๋ง) | 2~3h | |
| ์ดํฉ (์ ์ฒด) | 7~11h |
3B ์ฒ์๋ถํฐ ๊ฒฝ๋ก (๋์)
| ๋จ๊ณ | ์๊ฐ |
|---|---|
| 3B pretrain | 26h |
| SFT | 1~2h |
| ํ๊ฐ | 1h |
| ์ดํฉ | 28~29h |
๋น๊ต
| ํญ๋ชฉ | 1B ORPO | 3B ์ฒ์๋ถํฐ |
|---|---|---|
| ์์ ์๊ฐ | 2~11h | 28~29h |
| ์ฑ๊ณต ํ๋ฅ (<5%) | 70% | 80~90% |
| ์คํจ ์ ๋น์ฉ | 3~11h ๋ญ๋น | 29h ๋ญ๋น |
| ๊ธฐ๋๊ฐ (์๊ฐรํ๋ฅ ) | 3 |
29h / 0.85 = 34h |
| ๋ณ๋ ฌ ๊ฐ๋ฅ | โ 3B์ ๋์ ์งํ ๊ฐ๋ฅ | GPU ์ ์ |
6. ์ต์ข ๊ถ๊ณ : ์ ์ง๊ธ ๋น์ฅ ORPO์ฌ์ผ ํ๋๊ฐ
ํต์ฌ ๋ ผ๊ฑฐ
- ์๊ฐ ํจ์จ: ํ์ ๋จ๊ณ๋ง 2~3์๊ฐ. 3B์ 1/10 ์๊ฐ
- ๋ฆฌ์คํฌ ์ต์: ์คํจํด๋ 3์๊ฐ ์์ค. 3B๋ 29์๊ฐ ์์ค
- ์ด๋ฏธ ๋ฐ์ดํฐ ์์:
maywell/ko_Ultrafeedback_binarized61K์์ด HF์ ์ค๋น๋จ. ๋ค์ด๋ก๋๋ง ํ๋ฉด ๋จ - ์ ํํ ๋ฌธ์ ํด๊ฒฐ: ๋ฐ๋ณต ํดํ์ ์์ธ์ "๋ญ ํ๋ฉด ์ ๋๋์ง ๋ชจ๋ฆ" โ preference learning์ด ์ ํํ ํด๋ฒ
- ๋ณ๋ ฌ ์ ๋ต ๊ฐ๋ฅ: ORPO๋ 2~3์๊ฐ์ด๋ฏ๋ก, 3B ํ์ต๊ณผ ๋์์ ์์ ๊ฐ๋ฅ. ๋จผ์ ๋๋๋ ์ชฝ ์ฑํ
์ฆ์ ์คํ ๊ณํ
# Step 1: preference ๋ฐ์ดํฐ ์ค๋น (30๋ถ)
python3 scripts/prepare_orpo_data.py \
--hf_dataset maywell/ko_Ultrafeedback_binarized \
--sample_size 2000 \
--output data/orpo/train.jsonl
# Step 2: ORPO ํ์ต (1~2์๊ฐ)
python3 scripts/train_orpo.py \
--model checkpoints/korean_1b_sft/checkpoint-best \
--data data/orpo/train.jsonl \
--lr 5e-6 --epochs 2 --batch_size 4 --beta 0.1 \
--output checkpoints/korean_1b_orpo
# Step 3: ํ๊ฐ (30๋ถ)
python3 eval/comprehensive_eval.py \
--model checkpoints/korean_1b_orpo \
--repetition_penalty 1.2 --no_repeat_ngram_size 4
์ฑ๊ณต ํ์ ๊ธฐ์ค
| ์งํ | ๋ชฉํ | ํ์ฌ |
|---|---|---|
| ๋ฐ๋ณต๋ฅ | <5% | 18% |
| ์์ฐ ์ข ๋ฃ์จ | >80% | 60% |
| ์๋ต ํ์ง | ์ ์ง ๋๋ ๊ฐ์ | baseline |
์์ฝ
| ํญ๋ชฉ | ๊ฐ |
|---|---|
| ์ ๋ต | ORPO + ์ถ๋ก ํ๋ผ๋ฏธํฐ ํ๋ |
| ์์ ๋ฐ๋ณต๋ฅ | 3~7% (๋ชฉํ <5% ๋ฌ์ฑ ํ๋ฅ 70%) |
| ์ด ์์์๊ฐ | 2 |
| vs 3B | 10 |
| ํ์ ๋ฐ์ดํฐ | HF์์ ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅ (0์, 30๋ถ) |
| ํต์ฌ ๋ฉ์์ง | SFT๋ง์ผ๋ก๋ "ํ์ง ๋ง์์ผ ํ ๊ฒ"์ ๊ฐ๋ฅด์น ์ ์๋ค. ORPO๊ฐ ์ ํํ ํด๋ฒ์ด๋ค. |