Дофіно-tunuing LLM методом DPO (Direct Preference Optimization)
DPO — метод alignment, що дозволяє навчити модель генерувати переважні відповіді без явного навчання reward model та RLHF-циклу. Запропонований Rafailov et al. (Stanford, 2023), DPO перетворює задачу RL у задачу supervised learning на датасеті переваг (chosen/rejected пари), що суттєво спрощує пайплайн alignment.
DPO vs RLHF: принципова відмінність
RLHF (класичний):
- Навчання Reward Model на парах переваг
- Навчання LLM через PPO з використанням Reward Model
- KL-дивергенція від reference policy як регуляризатор
Недоліки: нестабільність PPO, необхідність тримати 4 моделі в пам'яті (actor, critic, reward, reference), складне налаштування.
DPO:
- Пряма оптимізація на парах (chosen, rejected) без Reward Model
- Неявний reward визначається через log-відношення ймовірностей trained/reference моделі
- Стабільне навчання як звичайний SFT
Математично DPO мінімізує:
L_DPO = -E[log σ(β * (log π_θ(y_w|x)/π_ref(y_w|x) - log π_θ(y_l|x)/π_ref(y_l|x)))]
де y_w — переважна відповідь, y_l — відхилена, β — температура KL-регуляризації.
Формат датасету для DPO
# Приклад запису датасету переваг
{
"prompt": "Поясни різницю між TCP та UDP",
"chosen": "TCP (Transmission Control Protocol) забезпечує надійну доставку даних з підтвердженням отримання, управлінням потоком та контролем помилок. UDP (User Datagram Protocol) — без встановлення з'єднання, без гарантії доставки, але з мінімальною затримкою. TCP використовують для HTTP, FTP, SMTP; UDP — для DNS, відео-streaming, ігор у реальному часі.",
"rejected": "TCP надійний, UDP швидкий. TCP повільніший, тому що перевіряє кожен пакет. Обидва це протоколи інтернету."
}
Реалізація DPO через TRL
from trl import DPOTrainer, DPOConfig
from peft import LoraConfig
# Створюємо reference model (заморожена копія SFT-моделі)
# TRL управляє цим автоматично при use_reference_model=True
dpo_config = DPOConfig(
output_dir="./dpo-model",
num_train_epochs=1, # DPO зазвичай 1-3 епохи
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=5e-7, # Значно нижче, ніж SFT
lr_scheduler_type="cosine",
warmup_ratio=0.1,
beta=0.1, # KL-температура
loss_type="sigmoid", # "sigmoid", "hinge", "ipo", "kto_pair"
max_length=2048,
max_prompt_length=512,
bf16=True,
logging_steps=10,
)
trainer = DPOTrainer(
model=model, # SFT-дофіно-tuned модель
ref_model=None, # None = автоматично створюється з model
args=dpo_config,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
peft_config=LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj","v_proj"]),
)
trainer.train()
Варіанти loss_type в DPO
- sigmoid: оригінальний DPO loss
- hinge: SLiC-HF, менш чутливий до outliers
- ipo: IPO (Identity Preference Optimization), стабільніша версія
- kto_pair: KTO (Kahneman-Tversky Optimization), працює з непарними даними
Створення датасету переваг: практичні методи
Метод 1: Human annotation. Найвища якість, але дорого. Анотатори бачать дві відповіді та вибирають кращу. Потрібно мінімум 2-3 анотатори на пару для надійності.
Метод 2: AI-генерація + human verification. GPT-4o генерує chosen (висока якість) та rejected (навмисне погіршена). Люди верифікують 20–30% вибірки.
Метод 3: Реальні дані з продакшену. Логи взаємодій з користувачами: лайки/дизлайки, рейтинги, коригування операторів.
from openai import OpenAI
def generate_preference_pair(prompt: str, client: OpenAI) -> dict:
"""Генерує пару chosen/rejected для DPO датасету"""
# Хороша відповідь
chosen_response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Дай детальну, точну, добре структуровану відповідь."},
{"role": "user", "content": prompt}
],
temperature=0.3
).choices[0].message.content
# Погана відповідь — навмисне погіршуємо якість
rejected_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Дай коротку, поверхневу відповідь без деталей."},
{"role": "user", "content": prompt}
],
temperature=0.9
).choices[0].message.content
return {"prompt": prompt, "chosen": chosen_response, "rejected": rejected_response}
Практичний case study: покращення якості обслуговування клієнтів
Задача: мовна модель для підтримки клієнтів відповідала коректно, але з жорстким, безособистісним тоном. SFT-дофіно-tuning на нових даних частково вирішив проблему, але вимагав пересбору даних щоразу.
Рішення: DPO на парах переваг. Chosen — відповіді операторів з високим CSAT. Rejected — відповіді з низьким CSAT. Обсяг: 2100 пар.
Базова модель для DPO: SFT-дофіно-tuned Mistral 7B.
Результати:
- CSAT боту: 3.4 → 4.2 (з 5)
- Empathy score (LLM-as-judge): 2.8 → 4.1
- Factual accuracy: без змін (0.91 → 0.91)
- Refusal rate: 12% → 4% (модель стала менш надмірно обережною)
- β=0.1 виявився оптимальним: при β=0.5 accuracy впала, при β=0.01 — нестабільність
Типовий пайплайн: SFT → DPO
DPO застосовується поверх SFT, а не замість нього:
- SFT (Supervised Fine-Tuning): навчаємо модель форматувати та видавати релевантні відповіді в домені
- DPO: вирівнюємо якість відповідей під переваги користувачів
Пропуск SFT і прямий DPO на базовій моделі технічно можливий, але менш стабільний.
Часові рамки
- Збір та розмітка датасету переваг: 3–6 тижнів
- SFT (якщо не проводився): 2–3 тижні
- DPO навчання та ітерації: 1–2 тижні
- Оцінка якості (LLM-as-judge + людина): 1 тиждень
- Всього: 7–12 тижнів







