Supervised Fine-Tuning mengajarkan model meniru jawaban yang ditulis penyusun data, tetapi menyamakan gerakan saja belum menyamakan selera: dua jawaban sama-sama valid secara tata bahasa, manusia tetap memilih salah satunya karena lebih jelas, lebih ringkas, atau lebih tepat sasaran. Sinyal “jawaban mana yang lebih baik” tidak bisa direpresentasikan sebagai target keras — sinyal itu berbentuk perbandingan. Bab ini menelusuri dua generasi solusi: RLHF yang mengubah perbandingan menjadi reward model lalu mengoptimalkannya dengan reinforcement learning, dan DPO yang membongkar rantai tersebut menjadi satu loss yang bekerja langsung pada pasangan preferensi.
RLHF: Tiga Tahap Klasik #
InstructGPT memperkenalkan resep yang menjadi standar industri selama bertahun-tahun, dan ketiga tahapnya masing-masing menyelesaikan masalah yang berbeda:
- SFT: model dasar dilatih mengikuti instruksi pada contoh jawaban berkualitas — modal awal berupa kemampuan format dan gaya.
- Reward model (RM): penyusun data menilai dua atau lebih keluaran untuk prompt yang sama; RM (biasanya model bahasa dengan kepala skalar) dilatih memprediksi skor sehingga keluaran pilihan manusia mendapat skor lebih tinggi.
- Optimasi kebijakan dengan PPO: LM dioptimalkan agar memaksimalkan skor RM, dengan penalti KL terhadap kebijakan awal agar model tidak menyimpang terlalu jauh dari kemampuan dasarnya — tanpa penalti ini model menemukan celah celah reward model yang tidak mencerminkan selera manusia.
Kompleksitas RLHF justru berada di tahap ketiga: PPO butuh kebijakan, reward model, dan model nilai dimuat bersamaan, gradiennya tidak stabil, dan hyperparameter-nya rewel. Tim DPO bertanya: bisakah tahap dua dan tiga dilebur?
DPO: Preferensi Tanpa Reward Model #
Insight DPO bermula dari hubungan antara reward dan kebijakan optimum: pada perumusan RLHF, reward dapat ditulis ulang sebagai β · log(π_θ(y)/π_ref(y)) ditambah konstanta yang tidak bergantung kebijakan. Substitusikan ke tujuan preferensi Bradley-Terry, dan reward model hilang sepenuhnya — yang tersisa adalah loss atas rasio log-likelihood dua kebijakan:
L_DPO = -E[ log σ( β · ( log π_θ(y+)/π_ref(y+) - log π_θ(y-)/π_ref(y-) ) ) ]
y+ : respons pilihan manusia (chosen)
y- : respons yang ditolak (rejected)
β : kendali seberapa jauh kebijakan boleh bergeserCara bacanya intuitif. Suku log π_θ(y)/π_ref(y) mengukur seberapa besar kebijakan baru meningkatkan probabilitas suatu respons dibanding kebijakan referensi. Loss menuntut selisih peningkatan itu lebih besar pada y+ daripada y- — model didorong menaikkan probabilitas jawaban pilihan dan menurunkan jawaban yang ditolak, relatif terhadap titik awal, dengan satuan ukur yang sama seperti reward. Fungsi σ menjaga pendorongan berhenti: begitu margin cukup positif, gradien melunak, sehingga β berperan seperti penalti KL pada RLHF — jangkar anti penyimpangan.
Demo: Margin yang Tumbuh dari Nol #
Kita akan membuktikan mekanisme itu pada LM mainan yang bisa diaudit penuh: model unigram atas delapan token. Respons adalah rangkaian tiga token; “penyusun data” disimulasikan dengan utilitas tersembunyi per token — pasangan chosen/rejected dibentuk dari respons yang utilitasnya berbeda jelas. Kebijakan awal seragam, referensi dibekukan, lalu theta dilatih hanya dengan loss DPO. Berkas dpo_mini.py menjalankan seluruh siklus:
"""DPO mini: melatih kebijakan bahasa langsung dari pasangan preferensi.
Bagian 1: LM mainan unigram atas 8 token; referensi = salinan kebijakan
awal yang dibekukan.
Bagian 2: bangun 48 pasangan (chosen, rejected) memakai utilitas tersembunyi
seolah skor penyusun data manusia.
Bagian 3: latih hanya dengan loss DPO tanpa reward model; margin
log-likelihood chosen-rejected dipantau membesar.
Bagian 4: baca reward tersirat (theta - theta_ref) / beta dan bandingkan
dengan utilitas sejati yang dipakai penyusun data.
"""
import numpy as np
import torch
import torch.nn.functional as F
torch.manual_seed(42)
rng = np.random.default_rng(42)
TOKEN = ["merah", "kuning", "hijau", "biru", "tenang", "cepat", "jelas", "penuh"]
U = np.array([0.1, -0.2, 0.15, -0.05, 0.9, -0.6, 0.8, -0.5]) # utilitas tersembunyi
V, L, N_PAIR, BETA = len(TOKEN), 3, 48, 0.5
# ---------- 1. Kebijakan dan referensi ----------
theta = torch.zeros(V, requires_grad=True) # kebijakan yang dilatih
theta_ref = torch.zeros(V) # referensi beku (kebijakan awal)
def log_pi(t, sekuens):
"""log p(sekuens) pada LM unigram: jumlah log-softmax token."""
return torch.log_softmax(t, dim=0)[sekuens].sum(dim=1)
def ke_string(sekuens):
return " ".join(TOKEN[i] for i in sekuens.tolist())
# ---------- 2. Pasangan preferensi ----------
pasangan = []
while len(pasangan) < N_PAIR:
a, b = rng.integers(0, V, size=L), rng.integers(0, V, size=L)
ua, ub = U[a].sum(), U[b].sum()
if abs(ua - ub) < 1.0: # pastikan kesenjangan jelas
continue
chosen, rejected = (a, b) if ua > ub else (b, a)
pasangan.append((chosen, rejected))
ch = torch.tensor(np.stack([p[0] for p in pasangan]), dtype=torch.long)
re = torch.tensor(np.stack([p[1] for p in pasangan]), dtype=torch.long)
print("== 2. contoh pasangan preferensi ==")
for i in range(3):
print(f" #{i} chosen : {ke_string(ch[i]):28s} util {U[ch[i]].sum():+.2f}")
print(f" rejected : {ke_string(re[i]):28s} util {U[re[i]].sum():+.2f}")
# ---------- 3. Latih dengan loss DPO ----------
opt = torch.optim.Adam([theta], lr=0.05)
print()
print("== 3. pelatihan DPO (beta = 0.5, tanpa reward model) ==")
print(f"{'epoch':>5s} {'loss':>8s} {'margin rata2':>13s} {'akurasi':>8s}")
for ep in range(1, 201):
opt.zero_grad()
margin = BETA * ((log_pi(theta, ch) - log_pi(theta_ref, ch))
- (log_pi(theta, re) - log_pi(theta_ref, re)))
loss = -F.logsigmoid(margin).mean()
loss.backward()
opt.step()
if ep in (1, 50, 100, 200):
with torch.no_grad():
akurasi = (margin > 0).float().mean().item()
print(f"{ep:5d} {loss.item():8.4f} {margin.mean().item():13.4f} "
f"{akurasi:8.3f}")
# ---------- 4. Reward tersirat vs utilitas sejati ----------
with torch.no_grad():
r_tersirat = (theta - theta_ref) / BETA
margin_akhir = BETA * ((log_pi(theta, ch) - log_pi(theta_ref, ch))
- (log_pi(theta, re) - log_pi(theta_ref, re)))
print()
print("== 4. reward tersirat per token vs utilitas penyusun data ==")
print(f"{'token':8s} {'utilitas':>9s} {'reward tersirat':>16s}")
for i in range(V):
print(f"{TOKEN[i]:8s} {U[i]:+9.2f} {r_tersirat[i].item():+16.2f}")
korelasi = np.corrcoef(U, r_tersirat.numpy())[0, 1]
print(f"korelasi Pearson utilitas vs reward tersirat: {korelasi:.3f}")
print(f"margin rata2: awal 0.0000 -> akhir {margin_akhir.mean().item():.4f}")Output yang Dihasilkan:
== 2. contoh pasangan preferensi ==
#0 chosen : merah tenang penuh util +0.50
rejected : merah cepat kuning util -0.70
#1 chosen : jelas tenang kuning util +1.50
rejected : cepat jelas cepat util -0.40
#2 chosen : jelas biru tenang util +1.65
rejected : hijau kuning penuh util -0.55
== 3. pelatihan DPO (beta = 0.5, tanpa reward model) ==
epoch loss margin rata2 akurasi
1 0.6931 0.0000 0.000
50 0.0975 2.5208 1.000
100 0.0438 3.5467 1.000
200 0.0189 4.6592 1.000
== 4. reward tersirat per token vs utilitas penyusun data ==
token utilitas reward tersirat
merah +0.10 +0.37
kuning -0.20 -4.19
hijau +0.15 -1.17
biru -0.05 -2.85
tenang +0.90 +6.80
cepat -0.60 -7.00
jelas +0.80 +6.69
penuh -0.50 -7.03
korelasi Pearson utilitas vs reward tersirat: 0.991
margin rata2: awal 0.0000 -> akhir 4.6677Mari kita telaah kode di atas baris per baris:
- Baris 28-31 (
thetadantheta_ref): kebijakan dilatih dan referensinya dibekukan — keduanya identik di titik awal, sehingga setiap rasioπ_θ/π_refbernilai tepat satu. Inilah jangkar yang menggantikan penalti KL milik PPO. - Baris 34-36 (
log_pi): LM unigram memberilog p(sekuens) = Σ log softmax(θ)[token]. Normalisasi softmax membuat rasio kebijakan bermakna: menaikkan probabilitas satu token selalu menurunkan yang lain, jadi “memilih” selalu ada harganya. - Baris 45-55 (pasangan): dua respons acak dibandingkan utilitas tersembunyinya; syarat selisih minimal 1,0 meniru penyusun data yang hanya memberi penilaian tegas pada pasangan yang jelas bedanya.
- Baris 62-64 (loss DPO persis):
marginadalah selisih peningkatan log-probabilitas chosen dan rejected dikali β;F.logsigmoidmembungkusnya menjadi loss. Pada epoch 1 margin tepat 0,0000 dan loss 0,6931 = log 2 — kondisi netral: kebijakan belum berpihak. - Baris 67-72 (monitor): margin tumbuh 0 → 2,52 → 4,66 dan akurasi preferensi mencapai 1,000: kebijakan kini menempatkan seluruh pasangan di pihak yang benar tanpa satu bobot reward model pun.
- Baris 75-91 (reward tersirat): membalik hubungan reward-kebijakan memberi
r̂ = (θ − θ_ref)/β. Peringkatnya cocok dengan utilitas sejati (korelasi Pearson 0,991) meski skalanya lebih tajam — tokentenang(util 0,90) dancepat(util −0,60) terpisah paling jauh, sementara token netral di tengah tidak terdorong jauh. Sifat memperbesar selisih inilah yang dikendalikan β: β kecil memperbolehkan geseran agresif, β besar menjaga kebijakan dekat referensi.
RLHF atau DPO #
| Aspek | RLHF (RM + PPO) | DPO |
|---|---|---|
| Komponen yang dilatih | reward model + kebijakan + model nilai | kebijakan saja |
| Sinyal data | perbandingan keluaran | pasangan chosen-rejected |
| Stabilitas pelatihan | rewel; sensitif hyperparameter PPO | stabil; loss seperti klasifikasi |
| Biaya memori inference | reward model tidak dibawa produksi | hanya kebijakan |
| Sampling on-policy saat latih | ya (PPO mengambil sampel dari kebijakan berjalan) | tidak (dataset statis) |
| Penyetelan β vs penalti KL | dua knob terpisah | satu knob terpadu |
Pilihan di lapangan sering kali pragmatis: banyak tim menjalankan DPO (atau varian turunannya seperti IPO dan KTO) setelah SFT karena biayanya mendekati fine-tuning biasa, lalu kembali ke RLHF penuh saat data preferensi harus dikumpulkan secara on-policy. Derivasi lengkap DPO ada di makalah Direct Preference Optimization , asal-usul resep tiga tahapnya di InstructGPT , dan implementasi siap pakai untuk keduanya tersedia di pustaka TRL milik Hugging Face.
Dengan SFT dan preference optimization, siklus penyetelan model selesai — model kini tahu mengikuti instruksi dan memilih jawaban yang disukai. Masalah berikutnya bersifat material: model 7B dalam fp16 memakan 14 GB memori setiap kali dijalankan. Bab berikutnya membongkar solusinya: Quantization LLM .