Post-Training LLM dengan TRL: SFT, Reward Modeling, dan DPO
Setelah sebuah base language model selesai dipretraining, model tersebut masih perlu dibentuk agar berguna dan selaras dengan ekspektasi manusia. TRL (Transformer Reinforcement Learning) adalah library dari Hugging Face untuk seluruh tahap post-training tersebut, mulai dari supervised fine-tuning sampai preference optimization. Tutorial ini membahas pipeline post-training modern dan menunjukkan cara memakai trainer-trainer TRL secara praktis, dengan Direct Preference Optimization (DPO) sebagai bagian utamanya.
Apa Itu TRL dan Posisinya
TRL adalah library yang berfokus pada langkah-langkah setelah pretraining. Tidak seperti wrapper yang sebagian besar hanya menyederhanakan konfigurasi, TRL menyediakan kelas-kelas trainer konkret untuk seluruh stack alignment: supervised fine-tuning, reward modeling, serta beberapa algoritma preference-optimization dan reinforcement learning.
TRL dibangun langsung di atas ekosistem Hugging Face:
- Transformers untuk memuat model dan tokenizer.
- PEFT untuk adapter yang hemat parameter seperti LoRA, sehingga model besar bisa dilatih di perangkat keras yang sederhana.
- Accelerate untuk pelatihan terdistribusi dan mixed-precision.
- Datasets untuk memuat dan memproses data.
Karena TRL memakai ulang komponen-komponen ini, semua hal yang sudah Anda ketahui tentang AutoModelForCausalLM, tokenizer, atau konfigurasi LoRA berlaku langsung di sini.
Pipeline Post-Training LLM Modern
Model instruction-following atau chat pada umumnya melewati tiga tahap:
TRL mencakup tahap 2 dan 3. Sebagian besar pekerjaan alignment di produksi saat ini menggabungkan satu putaran SFT yang diikuti satu putaran preference-optimization.
Instalasi
# Stack inti
pip install trl peft datasets accelerate
Ekstra yang direkomendasikan
pip install transformers bitsandbytes
pip install wandb # pelacakan eksperimen opsional
Verifikasi instalasi dan pastikan GPU terlihat:
import torch
import trl
print("Versi TRL:", trl.version)
print("CUDA tersedia:", torch.cuda.isavailable())
Trainer TRL bisa berjalan di CPU untuk eksperimen sangat kecil, tetapi post-training yang realistis membutuhkan GPU.
Format Dataset di TRL
TRL menstandarkan beberapa bentuk dataset. Menyiapkan format ini dengan benar adalah sebagian besar pekerjaannya.
- Format conversational (chat) untuk SFT: kolom
messagesberisi daftar dictionary{"role", "content"}. - Format preference untuk reward modeling dan DPO: kolom bernama
prompt,chosen, danrejected.
Format conversational terlihat seperti ini:
example = {
"messages": [
{"role": "system", "content": "Anda adalah asisten yang membantu."},
{"role": "user", "content": "Jelaskan apa itu vector database."},
{"role": "assistant", "content": "Vector database menyimpan embedding..."},
]
}
Format preference yang dipakai di sepanjang sisa tutorial ini:
example = {
"prompt": "Jelaskan apa itu vector database.",
"chosen": "Vector database menyimpan embedding dan mendukung similarity search...",
"rejected": "Itu cuma database biasa.",
}
TRL menerapkan chat template model secara otomatis ketika dataset berbentuk conversational, sehingga Anda jarang perlu memformat string secara manual.
Menyiapkan Data dengan Library datasets
Dalam praktik, data mentah Anda jarang datang dalam bentuk persis seperti yang diharapkan trainer. Library datasets membuat proses reshape menjadi murah, dan transformasinya berjalan secara lazy dan paralel.
from datasets import loaddataset
raw = loaddataset("json", datafiles="mypreferences.jsonl", split="train")
def topreference(example):
return {
"prompt": example["question"],
"chosen": example["goodanswer"],
"rejected": example["badanswer"],
}
dataset = raw.map(topreference, removecolumns=raw.columnnames)
Sisihkan split terpisah untuk evaluasi yang jujur.
splits = dataset.traintestsplit(testsize=0.05, seed=42)
traindataset, evaldataset = splits["train"], splits["test"]
Beberapa kebiasaan yang bermanfaat:
- Selalu sisihkan split terpisah. Metrik preference pada training set itu menyesatkan; Anda menginginkan angka eval yang belum pernah dilihat model.
- Periksa beberapa baris secara manual. Cetak lima contoh lalu baca. Label yang berisik jauh lebih mudah ditangkap dengan mata daripada lewat statistik agregat.
- Tetapkan seed. Split yang reproducible membuat eksperimen bisa dibandingkan antar-run.
Supervised Fine-Tuning dengan SFTTrainer
SFT adalah fondasinya: tahap ini mengajarkan base model gaya respons dan perilaku mengikuti instruksi yang Anda inginkan sebelum preference tuning apa pun. Di TRL ini adalah SFTTrainer yang dikonfigurasi dengan SFTConfig.
from datasets import loaddataset
from trl import SFTConfig, SFTTrainer
dataset = load
dataset("trl-lib/Capybara", split="train")
config = SFTConfig(
outputdir="sft-model",
numtrainepochs=1,
perdevicetrainbatchsize=2,
gradientaccumulationsteps=8,
learningrate=2e-5,
loggingsteps=10,
packing=True, # gabungkan sampel pendek agar context window efisien
maxlength=1024,
bf16=True,
)
trainer = SFTTrainer(
model="Qwen/Qwen2.5-0.5B",
args=config,
traindataset=dataset,
)
trainer.train()
trainer.savemodel("sft-model")
Beberapa hal yang perlu diperhatikan:
- Chat template. Ketika dataset memiliki kolom
messages,SFTTrainermenerapkan chat template dari tokenizer, sehingga token khusus dan penanda peran ditambahkan dengan benar. - Packing. Menyetel
packing=Truemenggabungkan beberapa contoh pendek menjadi satu sekuens hinggamaxlength. Ini meningkatkan throughput dan mengurangi padding yang terbuang, dengan konsekuensi sedikit mencampur contoh di dalam satu sekuens. - Memberikan nama model. Anda bisa memberikan string ID model lalu TRL memuatnya untuk Anda, atau memberikan instance
AutoModelForCausalLMyang sudah dimuat.
Menggabungkan SFT dengan LoRA
Untuk model yang lebih besar, biasanya Anda tidak ingin full fine-tuning. TRL menerima peftconfig, dan trainer membungkus model dengan PEFT secara internal. LoRA dan PEFT dibahas mendalam di tutorial terpisah, jadi bagian ini sengaja dibuat singkat.
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer
peftconfig = LoraConfig(
r=16,
loraalpha=32,
loradropout=0.05,
targetmodules="all-linear",
tasktype="CAUSALLM",
)
trainer = SFTTrainer(
model="Qwen/Qwen2.5-0.5B",
args=SFTConfig(outputdir="sft-lora", bf16=True),
traindataset=dataset,
peftconfig=peftconfig,
)
trainer.train()
Argumen peftconfig yang sama berlaku untuk trainer TRL lainnya, termasuk DPOTrainer.
Reward Modeling dengan RewardTrainer
RLHF klasik membutuhkan reward model: sebuah model yang menerima prompt dan respons lalu mengeluarkan skor skalar yang mencerminkan seberapa baik respons tersebut. Anda melatihnya pada data preference, mengajarkannya untuk memberi skor chosen lebih tinggi daripada rejected.
from datasets import loaddataset
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from trl import RewardConfig, RewardTrainer
modelid = "Qwen/Qwen2.5-0.5B"
tokenizer = AutoTokenizer.frompretrained(modelid)
model = AutoModelForSequenceClassification.frompretrained(modelid, numlabels=1)
dataset = loaddataset("trl-lib/ultrafeedbackbinarized", split="train")
config = RewardConfig(
outputdir="reward-model",
perdevicetrainbatchsize=4,
numtrainepochs=1,
learningrate=1e-5,
loggingsteps=10,
maxlength=1024,
bf16=True,
)
trainer = RewardTrainer(
model=model,
args=config,
traindataset=dataset,
processingclass=tokenizer,
)
trainer.train()
Reward model dimuat sebagai head sequence-classification dengan satu output (numlabels=1). Trainer mengoptimalkan loss berpasangan sehingga skor untuk chosen melebihi skor untuk rejected. Reward model inilah yang nantinya dipakai oleh PPOTrainer untuk memberikan sinyal pelatihan.
Direct Preference Optimization (DPO)
DPO adalah metode yang paling sering dipilih tim ketika ingin menyelaraskan model dengan preferensi. Ini adalah bagian utama tutorial ini.
Apa Itu DPO dan Mengapa Membantu
RLHF tradisional adalah proses banyak tahap: melatih reward model, lalu menjalankan reinforcement learning (PPO) di mana policy menghasilkan respons, reward model memberi skor, dan policy diperbarui. Loop tersebut kuat tetapi berat secara operasional. Ia membutuhkan reward model terpisah, generasi online selama pelatihan, dan tuning yang hati-hati agar tetap stabil.
DPO membingkai ulang masalah ini. Alih-alih melatih reward model lalu mengoptimalkannya dengan RL, DPO menurunkan sebuah loss yang bekerja langsung pada pasangan preference. Loss ini menaikkan log-probability relatif yang diberikan policy pada respons chosen dibanding rejected, sementara sebuah reference model yang dibekukan menjaga policy agar tidak menyimpang terlalu jauh dari titik awalnya.
Konsekuensi praktisnya:
- Tidak ada reward model terpisah yang harus dilatih dan dilayani.
- Tidak ada loop generasi online selama pelatihan, sehingga lebih sederhana dan stabil.
- Berjalan layaknya job pelatihan bergaya supervised pada dataset preference.
DPO tetap bergantung pada reference model — biasanya model SFT yang menjadi titik awal Anda — yang dimuat otomatis ketika Anda tidak memberikannya secara eksplisit.
Dataset Preference untuk DPO
DPO mengharapkan format prompt / chosen / rejected. Banyak dataset publik sudah disediakan dalam bentuk ini:
from datasets import loaddataset
dataset = loaddataset("trl-lib/ultrafeedbackbinarized", split="train")
print(dataset[0].keys()) # dictkeys(['prompt', 'chosen', 'rejected', ...])
Jika Anda membangun data sendiri, setiap baris memasangkan satu prompt dengan satu completion yang disukai dan satu yang tidak disukai. Kualitas dan konsistensi pasangan ini lebih penting daripada kuantitas mentahnya.
Contoh DPO Lengkap
Contoh ini menyelaraskan sebuah model instruct kecil dengan dataset preference. Dalam praktik, Anda akan menjalankan SFT terlebih dahulu dan mengarahkan DPO ke checkpoint SFT tersebut; di sini kita memakai model yang sudah instruct demi keringkasan.
from datasets import loaddataset
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import DPOConfig, DPOTrainer
modelid = "Qwen/Qwen2.5-0.5B-Instruct"
model = AutoModelForCausalLM.frompretrained(modelid)
tokenizer = AutoTokenizer.frompretrained(modelid)
traindataset = loaddataset(
"trl-lib/ultrafeedbackbinarized", split="train"
).select(range(2000)) # potongan kecil untuk percobaan cepat
config = DPOConfig(
outputdir="dpo-model",
perdevicetrainbatchsize=2,
gradientaccumulationsteps=8,
numtrainepochs=1,
learningrate=5e-6,
beta=0.1, # kekuatan KL: seberapa dekat tetap ke reference
losstype="sigmoid", # loss DPO standar
loggingsteps=10,
maxlength=1024,
maxpromptlength=512,
bf16=True,
)
trainer = DPOTrainer(
model=model,
refmodel=None, # TRL membuat salinan beku dari model sebagai reference
args=config,
traindataset=traindataset,
processingclass=tokenizer,
)
trainer.train()
trainer.savemodel("dpo-model")
Pengaturan DPOConfig yang penting:
betamengontrol seberapa kuat policy ditahan pada reference model. Nilai lebih rendah (sekitar 0,05) memungkinkan perubahan lebih besar; nilai lebih tinggi (0,3+) membuat model konservatif. Nilai sekitar 0,1 adalah titik awal yang umum.losstypememilih loss preference."sigmoid"adalah DPO standar; alternatif seperti"ipo"dan"hinge"mengubah cara objektif memperlakukan margin preference dan dapat mengurangi overfitting pada beberapa dataset.refmodel=Nonememberitahu TRL untuk membuat salinan beku dari policy sebagai reference. Jika Anda melatih dengan LoRA, TRL bisa memakai base model dengan adapter dinonaktifkan sebagai reference, sehingga tidak perlu salinan model kedua di memori.
Untuk menjalankan DPO dengan LoRA alih-alih full fine-tuning, berikan peftconfig persis seperti pada contoh SFT. Ini adalah setup umum untuk model yang lebih besar.
Cara Membaca Log Pelatihan DPO
DPO mengeluarkan metrik yang memberi tahu apakah run-nya sehat, di luar loss mentah. Yang paling informatif:
rewards/accuracy— proporsi pasangan di mana model memberikan implicit reward lebih tinggi padachosendibandingrejected. Nilainya harus naik di atas 0,5 dan terus menanjak. Jika tetap di sekitar 0,5, model tidak belajar preferensinya.rewards/margins— rata-rata selisih antara implicit reward chosen dan rejected. Margin yang membesar menandakan model memisahkan keduanya dengan lebih percaya diri.rewards/chosendanrewards/rejected— implicit reward itu sendiri. Perhatikan hubungan di antara keduanya, bukan nilai absolutnya.
Tanda peringatan yang umum adalah reward accuracy yang naik cepat mendekati 1,0 dalam beberapa ratus step pada dataset kecil. Itu biasanya berarti overfitting alih-alih alignment yang sesungguhnya, dan menjadi isyarat untuk mengurangi epoch atau menambah data.
Sekilas Trainer Preference dan RL Lainnya
DPO bukan satu-satunya opsi. TRL menyertakan beberapa trainer, dan pilihan yang tepat bergantung pada data dan batasan Anda.
PPOTrainer — RLHF Klasik
PPOTrainer mengimplementasikan pendekatan tradisional reward-model-plus-RL. Policy menghasilkan respons, reward model memberi skor, dan Proximal Policy Optimization memperbarui policy. Ini metode paling fleksibel dan bisa mengoptimalkan terhadap sinyal reward apa pun, tetapi paling sulit di-tuning dan paling boros sumber daya karena generasi terjadi di dalam loop pelatihan. Gunakan PPO ketika Anda sudah memiliki reward model terlatih dan membutuhkan optimasi online yang tidak bisa dinyatakan oleh DPO.
GRPOTrainer — Group Relative Policy Optimization
GRPOTrainer adalah metode RL yang menghilangkan kebutuhan akan jaringan value/critic terpisah. Untuk setiap prompt, ia mengambil sampel sebuah grup completion, memberi skor, dan memakai skor relatif grup tersebut untuk mengestimasi advantage. Metode ini berpasangan secara alami dengan reward function ketimbang reward model terlatih, sehingga sangat cocok untuk tugas dengan reward yang dapat diverifikasi — matematika, kode, atau apa pun yang bisa diperiksa secara programatik.
from datasets import loaddataset
from trl import GRPOConfig, GRPOTrainer
dataset = loaddataset("trl-lib/tldr", split="train")
Reward function menerima completion yang dihasilkan dan mengembalikan skor per item.
def lengthreward(completions, kwargs):
# Contoh sederhana: lebih suka jawaban ringkas di bawah 200 karakter.
return [1.0 if len(c) < 200 else 0.0 for c in completions]
trainer = GRPOTrainer(
model="Qwen/Qwen2.5-0.5B-Instruct",
rewardfuncs=lengthreward,
args=GRPOConfig(outputdir="grpo-model", bf16=True, loggingsteps=10),
traindataset=dataset,
)
trainer.train()
Reward yang dapat diverifikasi dan lebih realistis memeriksa kebenaran terhadap jawaban ground-truth:
def correctnessreward(completions, groundtruth, kwargs):
scores = []
for completion, answer in zip(completions, ground
truth):
scores.append(1.0 if answer.strip() in completion else 0.0)
return scores
Kolom dataset tambahan (seperti groundtruth) diteruskan ke reward function sebagai keyword argument, sehingga Anda bisa mengimplementasikan objektif apa pun yang bisa diperiksa.
ORPO dan KTO
Dua metode lain yang perlu diketahui:
- ORPO (Odds Ratio Preference Optimization) melipat preference optimization ke dalam langkah SFT itu sendiri, menghilangkan kebutuhan akan reference model terpisah dan fase alignment terpisah.
- KTO (Kahneman-Tversky Optimization) bekerja dengan feedback biner yang lebih sederhana — satu completion yang diberi label baik atau buruk — alih-alih data chosen/rejected berpasangan, yang berguna ketika mengumpulkan pasangan eksplisit itu sulit.
Keduanya memiliki kelas ORPOTrainer/ORPOConfig dan KTOTrainer/KTOConfig yang mengikuti pola penggunaan sama seperti trainer di atas.
Logging, Evaluasi, dan Hub
Trainer TRL terintegrasi dengan perkakas standar Hugging Face.
config = DPOConfig(
outputdir="dpo-model",
evalstrategy="steps",
evalsteps=100,
loggingsteps=10,
reportto="wandb", # atau "tensorboard"
pushtohub=True,
hubmodelid="username-anda/dpo-model",
)
Dengan split evaluasi yang diberikan sebagai evaldataset, DPO mencatat metrik berguna seperti reward accuracy (seberapa sering chosen mendapat skor di atas rejected) dan reward margin. Setelah pelatihan, Anda bisa mendorong model:
trainer.pushtohub()
Pastikan Anda sudah terautentikasi terlebih dahulu dengan huggingface-cli login. Repository yang didorong mencakup bobot, tokenizer, dan model card yang dihasilkan otomatis.
Penskalaan dengan Accelerate dan DeepSpeed
Untuk pelatihan multi-GPU atau dengan keterbatasan memori, jalankan dengan Accelerate alih-alih python:
accelerate config # setup interaktif sekali saja
accelerate launch traindpo.py
Untuk model besar yang tidak muat dalam satu GPU, DeepSpeed ZeRO membagi state optimizer, gradien, dan parameter ke beberapa perangkat:
accelerate launch --configfile deepspeedzero3.yaml traindpo.py
Karena TRL dibangun di atas Accelerate, skrip pelatihan Anda tidak berubah — hanya perintah peluncuran dan konfigurasinya. Repository TRL menyertakan contoh file konfigurasi Accelerate dan DeepSpeed yang bisa Anda adaptasi.
Praktik Terbaik dan Jebakan Umum
- Lakukan SFT sebelum DPO. Preference optimization menyesuaikan model yang sudah mampu. Menjalankan DPO pada base model yang belum belajar format chat biasanya menghasilkan hasil yang buruk. Perlakukan SFT sebagai prasyarat, bukan langkah opsional.
- Tuning
betadengan sengaja. Terlalu rendah dan model menyimpang dari reference, menurunkan kemampuan umum sambil mengejar sinyal preference. Terlalu tinggi dan model nyaris tidak bergerak. Mulai dari 0,1 dan sesuaikan berdasarkan reward accuracy serta pemeriksaan kualitatif. - Perhatikan reference model. DPO membutuhkan reference. Dengan full fine-tuning, itu berarti salinan beku kedua di memori; dengan LoRA, menonaktifkan adapter menyediakan reference secara gratis. Rencanakan anggaran memori Anda sesuai dengan itu.
- Prioritaskan kualitas dataset. Data preference lebih berisik dari yang terlihat. Pasangan
chosen/rejectedyang tidak konsisten atau kontradiktif mengajarkan model sinyal yang saling bertentangan. Dataset kecil yang bersih dan diberi label konsisten mengalahkan dataset besar yang berisik. - Waspadai overfitting. Preference optimization bisa cepat overfit, terutama pada dataset kecil. Jaga epoch tetap rendah (sering kali satu sudah cukup), gunakan split eval terpisah, dan pantau reward accuracy bersama kualitas generasi alih-alih percaya pada satu angka saja.
- Samakan chat template. SFT, reference model, dan DPO harus memakai chat template dan tokenizer yang sama. Ketidakcocokan secara diam-diam merusak format yang diharapkan model dan menurunkan hasil tanpa peringatan.
- Pertahankan learning rate kecil. Post-training memakai learning rate jauh lebih rendah daripada pretraining — biasanya
1e-6sampai5e-6untuk DPO. Nilai yang lebih tinggi cenderung mengganggu kestabilan alignment. - Evaluasi melampaui metrik. Reward accuracy bisa terlihat sehat sementara generasi justru bergeser ke arah terlalu bertele-tele atau menolak menjawab. Selalu ambil sampel completion nyata pada prompt terpisah dan baca isinya sebelum menyatakan sebuah run berhasil.
Kesimpulan dan Poin Penting
TRL memberi Anda satu toolkit yang konsisten untuk seluruh pipeline post-training LLM, dibangun di atas stack Transformers, PEFT, dan Accelerate yang sudah Anda pakai.
- Post-training memiliki tiga tahap: pretrain, SFT, lalu preference optimization. TRL mencakup dua tahap terakhir.
SFTTrainermenangani supervised fine-tuning, menerapkan chat template, mendukung packing, dan berpadu rapi dengan LoRA melaluipeftconfig.RewardTrainermelatih reward model skalar pada pasangan chosen/rejected untuk RLHF klasik.DPOTraineradalah default praktis untuk alignment preference: ia mengoptimalkan langsung pada pasangan preference, menghindari reward model terpisah dan loop RL, serta di-tuning terutama lewatbetadanloss_type.PPOTrainer,GRPOTrainer,ORPO, danKTOmencakup sisa spektrumnya, dari RLHF penuh sampai RL dengan reward terverifikasi hingga metode feedback biner.- Keberhasilan lebih bergantung pada melakukan SFT lebih dahulu, mengurasi data preference yang bersih, men-tuning
beta, dan menjaga dari overfitting ketimbang pada algoritmanya sendiri.
Mulailah dengan satu putaran SFT, lanjutkan ke DPO pada dataset preference yang bersih, evaluasi secara jujur, dan baru beralih ke PPO atau GRPO ketika tugas Anda benar-benar membutuhkan reinforcement learning online.