CloudCodeTree LogoCloudCodeTree
AI NewsTutorialsAbout
CloudCodeTree Logo
CloudCodeTree
  • AI News
  • Tutorials
  • About
← Back to AI News
You've SFT'd Your Model. Now Tell It What "Better" Means: DPO With TRL in 8 Lines

You've SFT'd Your Model. Now Tell It What "Better" Means: DPO With TRL in 8 Lines

Chris Harper

3 min read

Sep 1, 2026 · 12:05 UTC

AI
Tutorial
Fine-Tuning
HuggingFace

TL;DR: DPO trains a model on preferred/rejected response pairs — no reward model, no RL loop needed. TRL's DPOTrainer runs on LoRA adapters in 8 lines and works on any HuggingFace causal LM.

What you'll be able to do after this:

  • Run preference alignment (DPO) on any HuggingFace causal LM with a LoRA adapter
  • Build a minimal preference dataset from your own domain
  • Understand when DPO adds value on top of SFT and when SFT alone is enough

Why DPO

SFT teaches the model "here is the correct output" — it requires labeled ground-truth answers. DPO teaches the model "here is what's preferred" — it requires pairs of responses to the same prompt: a better one (chosen) and a worse one (rejected). DPO fits quality judgements (writing style, tone, instruction following) where you can compare two responses but can't easily produce a perfect one from scratch.

The mechanism: DPO directly optimizes the policy to widen the log-likelihood margin between chosen and rejected responses, relative to a reference model, without training a separate reward model first. Computationally cheaper than RLHF; on par or better in most alignment tasks.

The dataset format

Three columns — prompt, chosen, rejected:

# Conversational format (recommended)
example = {
    "prompt": [{"role": "user", "content": "Explain gradient descent."}],
    "chosen": [{"role": "assistant", "content": "Gradient descent minimizes loss by moving parameters in the direction of the negative gradient..."}],
    "rejected": [{"role": "assistant", "content": "It's when the model learns stuff..."}],
}

The trl-lib/ultrafeedback_binarized dataset on HuggingFace Hub is a solid public starting point (~60k pairs across diverse prompts).

8 lines to train

from trl import DPOTrainer, DPOConfig
from datasets import load_dataset
from peft import LoraConfig

dataset = load_dataset("trl-lib/ultrafeedback_binarized", split="train")

trainer = DPOTrainer(
    "Qwen/Qwen3-0.6B",            # any causal LM by name or path
    train_dataset=dataset,
    peft_config=LoraConfig(),      # LoRA keeps VRAM manageable on consumer GPUs
    args=DPOConfig(
        beta=0.1,                  # controls deviation from reference model
        learning_rate=1e-5,        # higher than full fine-tune (adapter params only)
        num_train_epochs=1,
    ),
)
trainer.train()

Building your own preference dataset

DomainchosenrejectedHow to label
Codepasses testsfails testsautomated test runner
Writingfollows house styleignores itLLM-as-judge with your rubric
Factual QAverified answerplausible but wronghuman review or retrieval

For code, test results are a free labeling signal: generate 4 responses per prompt from your SFT model, run tests, label passing as chosen and failing as rejected. Phil Schmid's walk-through on using synthetic preference data shows ~2,000 pairs yielding a 5% benchmark improvement on reasoning tasks.

When DPO beats SFT alone

DPO is most useful after SFT, not instead of it. SFT gets the model producing plausible outputs; DPO refines the distribution toward your quality standard. DPO without SFT often produces artifacts because the reference model (the pre-trained base) is too far from the target distribution.

Limits

  • Double VRAM: DPO keeps a reference model alongside the policy during training. Use precompute_ref_log_probs=True to pre-compute reference log-probs and free the reference model, or Unsloth for ~70% less VRAM overall.
  • Preference quality beats quantity. 1,000 clean pairs typically outperform 100,000 noisy ones. Contradictory labels degrade alignment instead of improving it.
  • Won't rescue a bad base. DPO shifts the distribution; it doesn't rebuild a model that wasn't pre-trained or SFT'd adequately.

Sources: DPO Trainer — HuggingFace TRL docs · How to align open LLMs with DPO and synthetic data — philschmid.de · Preference alignment — smol-course (HuggingFace)