Split View: RLHF에서 DPO까지: LLM 정렬(Alignment) 기술 논문 심층 분석
RLHF에서 DPO까지: LLM 정렬(Alignment) 기술 논문 심층 분석
- 들어가며
- LLM 정렬 문제 정의
- RLHF 파이프라인 심층 분석
- Constitutional AI: AI 피드백 기반 정렬
- DPO: 수학적 기반과 구현
- PPO 학습 안정성
- 최신 정렬 기법: KTO, IPO, ORPO
- 비교 분석
- 실무 적용 가이드
- 실패 사례와 교훈
- 참고자료

들어가며
대규모 언어 모델(LLM)은 방대한 코퍼스를 학습하여 놀라운 언어 능력을 갖추지만, 사전학습만으로는 인간의 의도와 가치에 부합하는 출력을 보장할 수 없다. 유해한 콘텐츠를 생성하거나, 사용자의 지시를 무시하거나, 사실이 아닌 정보를 자신 있게 서술하는 문제가 빈번하게 발생한다. 이러한 간극을 해소하기 위한 연구 분야가 바로 LLM 정렬(Alignment) 이다.
2022년 OpenAI의 InstructGPT 논문이 RLHF(Reinforcement Learning from Human Feedback) 파이프라인을 체계화한 이후, Anthropic의 Constitutional AI, Stanford의 DPO(Direct Preference Optimization), 그리고 KTO, IPO, ORPO 등 다양한 후속 기법이 등장했다. 이 글에서는 이들 핵심 논문의 수학적 기반, 구현 방식, 실무 적용 시 주의점을 체계적으로 분석한다.
LLM 정렬 문제 정의
왜 정렬이 필요한가
사전학습된 LLM은 인터넷 텍스트의 다음 토큰 예측을 최적화한다. 이 목표는 인간이 원하는 "도움이 되고(Helpful), 정직하며(Honest), 무해한(Harmless)" 출력과 직접적으로 대응하지 않는다. 구체적으로 세 가지 핵심 문제가 있다.
- 유용성(Helpfulness): 사용자의 지시를 정확히 이해하고 수행하는 능력
- 정직성(Honesty): 모르는 것을 모른다고 인정하고, 환각을 최소화하는 능력
- 무해성(Harmlessness): 유해하거나 편향된 콘텐츠 생성을 거부하는 능력
정렬의 수학적 프레임워크
정렬 문제는 보상 함수 r(x, y)를 기반으로 정책 최적화 문제로 형식화된다. 프롬프트 x가 주어졌을 때, 정책 pi가 생성하는 응답 y에 대해 보상을 최대화하되, 사전학습된 참조 정책과의 괴리를 제한한다.
여기서 beta는 KL 발산 페널티의 강도를 조절하는 하이퍼파라미터이다. beta가 너무 작으면 보상 해킹(reward hacking)이 발생하고, 너무 크면 학습이 느려진다.
RLHF 파이프라인 심층 분석
InstructGPT의 3단계 파이프라인
Ouyang et al.(2022)의 InstructGPT 논문은 RLHF 파이프라인을 세 단계로 체계화했다.
1단계: 지도 미세조정(SFT)
인간 레이블러가 작성한 시연 데이터로 기본 모델을 미세조정한다. 이 단계에서 모델은 지시를 따르는 기본 능력을 학습한다.
2단계: 보상 모델(RM) 학습
동일 프롬프트에 대해 여러 응답을 생성한 뒤, 인간 레이블러가 선호도 순위를 매긴다. 이 데이터로 보상 모델을 학습한다.
3단계: PPO를 통한 강화학습
보상 모델의 피드백을 이용하여 PPO 알고리즘으로 정책을 최적화한다.
보상 모델 학습 구현
보상 모델은 Bradley-Terry 모델을 기반으로, 선호 쌍(chosen, rejected)에 대한 로그 확률 차이를 최대화하도록 학습된다.
import torch
import torch.nn as nn
from transformers import AutoModelForSequenceClassification, AutoTokenizer
class RewardModel(nn.Module):
def __init__(self, model_name="gpt2"):
super().__init__()
self.model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=1
)
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
return outputs.logits.squeeze(-1)
def reward_model_loss(reward_model, chosen_ids, chosen_mask, rejected_ids, rejected_mask):
"""Bradley-Terry 선호 모델 기반 보상 모델 손실 함수"""
r_chosen = reward_model(chosen_ids, chosen_mask)
r_rejected = reward_model(rejected_ids, rejected_mask)
# 선호 응답의 보상이 비선호 응답보다 높도록 학습
loss = -torch.log(torch.sigmoid(r_chosen - r_rejected)).mean()
accuracy = (r_chosen > r_rejected).float().mean()
return loss, accuracy
선호 데이터셋 구성
RLHF 파이프라인의 핵심은 고품질 선호 데이터셋 구성이다. 각 샘플은 프롬프트, 선호 응답, 비선호 응답의 삼중쌍으로 구성된다.
from datasets import Dataset
def build_preference_dataset(prompts, model, tokenizer, num_samples=4):
"""프롬프트별로 여러 응답을 생성하고 선호 쌍을 구성하는 파이프라인"""
preference_data = []
for prompt in prompts:
inputs = tokenizer(prompt, return_tensors="pt")
responses = []
# 다양한 응답 생성 (temperature sampling)
for _ in range(num_samples):
output = model.generate(
**inputs,
max_new_tokens=256,
temperature=0.8,
do_sample=True,
)
response = tokenizer.decode(output[0], skip_special_tokens=True)
responses.append(response)
# 실제 파이프라인에서는 인간 레이블러가 순위를 매김
# 여기서는 보상 모델 점수로 대체
scored = [(r, reward_model_score(prompt, r)) for r in responses]
scored.sort(key=lambda x: x[1], reverse=True)
# 최상위-최하위 쌍을 선호 데이터로 구성
preference_data.append({
"prompt": prompt,
"chosen": scored[0][0],
"rejected": scored[-1][0],
})
return Dataset.from_list(preference_data)
Constitutional AI: AI 피드백 기반 정렬
Anthropic의 접근법
Bai et al.(2022)의 Constitutional AI는 RLHF의 인간 레이블링 의존성을 줄이기 위한 혁신적인 접근법이다. 핵심 아이디어는 AI 스스로가 자신의 출력을 비판하고 개선하도록 하는 것이다. 이 과정은 사전 정의된 원칙(Constitution)에 의해 안내된다.
두 단계 프로세스
Supervised Learning 단계 (자기 비판 및 수정):
- 모델이 유해할 수 있는 응답을 생성한다
- 헌법 원칙에 따라 자신의 응답을 비판한다
- 비판을 반영하여 수정된 응답을 생성한다
- 수정된 응답으로 SFT를 수행한다
RL 단계 (RLAIF - RL from AI Feedback):
- 두 응답 중 어느 것이 원칙에 더 부합하는지 AI가 판단한다
- 이 AI 선호 데이터로 보상 모델을 학습한다
- 보상 모델을 이용하여 PPO로 정책을 최적화한다
Constitutional AI의 가장 큰 장점은 인간 레이블링 비용을 대폭 절감하면서도, 원칙 기반의 투명하고 해석 가능한 정렬을 달성한다는 점이다.
DPO: 수학적 기반과 구현
RLHF 목적함수의 해석적 해
Rafailov et al.(2023)의 DPO는 RLHF의 가장 우아한 대안이다. 핵심 통찰은 KL 제약이 있는 보상 최대화 문제에 해석적 해가 존재한다는 것이다.
최적 정책은 다음과 같은 형태를 갖는다.
이를 역으로 풀면, 보상 함수를 정책의 비율로 표현할 수 있다.
이를 Bradley-Terry 모델에 대입하면, 보상 모델 없이 직접 정책을 최적화하는 DPO 손실함수가 도출된다.
DPO 학습 루프 구현
import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
def compute_dpo_loss(
policy_model,
reference_model,
chosen_ids,
chosen_mask,
rejected_ids,
rejected_mask,
beta=0.1,
):
"""DPO 손실 함수: 보상 모델 없이 직접 정책 최적화"""
# 정책 모델의 로그 확률 계산
policy_chosen_logps = get_log_probs(policy_model, chosen_ids, chosen_mask)
policy_rejected_logps = get_log_probs(policy_model, rejected_ids, rejected_mask)
# 참조 모델의 로그 확률 계산 (gradient 불필요)
with torch.no_grad():
ref_chosen_logps = get_log_probs(reference_model, chosen_ids, chosen_mask)
ref_rejected_logps = get_log_probs(reference_model, rejected_ids, rejected_mask)
# DPO 로그 비율 계산
chosen_logratios = policy_chosen_logps - ref_chosen_logps
rejected_logratios = policy_rejected_logps - ref_rejected_logps
# DPO 손실: -log(sigmoid(beta * (chosen_logratio - rejected_logratio)))
logits = beta * (chosen_logratios - rejected_logratios)
loss = -F.logsigmoid(logits).mean()
# 메트릭: 선호 응답의 암묵적 보상이 더 높은 비율
reward_accuracy = (logits > 0).float().mean()
return loss, reward_accuracy
def get_log_probs(model, input_ids, attention_mask):
"""시퀀스의 토큰별 로그 확률 합산"""
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
logits = outputs.logits[:, :-1, :]
labels = input_ids[:, 1:]
log_probs = F.log_softmax(logits, dim=-1)
selected_log_probs = torch.gather(
log_probs, dim=-1, index=labels.unsqueeze(-1)
).squeeze(-1)
mask = attention_mask[:, 1:]
return (selected_log_probs * mask).sum(dim=-1)
DPO의 장단점
장점:
- 보상 모델 학습이 불필요하여 파이프라인이 단순해짐
- PPO 대비 메모리 사용량이 적음 (2개 모델 vs 4개 모델)
- 하이퍼파라미터 튜닝이 상대적으로 간단
단점:
- 참조 모델과의 분포 이동(distribution shift)에 취약
- 오프라인 데이터에 의존하여 탐색(exploration)이 부족
- 선호 데이터의 품질에 매우 민감
PPO 학습 안정성
PPO in RLHF: 구현의 핵심
PPO(Proximal Policy Optimization)는 RLHF에서 가장 널리 사용되는 강화학습 알고리즘이다. 핵심은 클리핑 메커니즘으로, 정책 업데이트의 크기를 제한하여 학습 안정성을 보장한다.
import torch
import torch.nn.functional as F
def ppo_step(
policy_model,
value_model,
old_log_probs,
states,
actions,
rewards,
advantages,
clip_epsilon=0.2,
value_clip=0.2,
kl_coeff=0.1,
ref_log_probs=None,
):
"""PPO 업데이트 스텝 (LLM RLHF용)"""
# 현재 정책의 로그 확률
new_log_probs = policy_model.get_log_probs(states, actions)
# 중요도 비율 계산
ratio = torch.exp(new_log_probs - old_log_probs)
# 클리핑된 대리 목적함수
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 + clip_epsilon) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
# 가치 함수 손실 (클리핑 적용)
new_values = value_model(states)
value_loss = F.mse_loss(new_values, rewards)
# KL 발산 페널티 (참조 모델 대비)
kl_penalty = 0.0
if ref_log_probs is not None:
kl_div = (old_log_probs - ref_log_probs).mean()
kl_penalty = kl_coeff * kl_div
total_loss = policy_loss + 0.5 * value_loss + kl_penalty
return total_loss, {
"policy_loss": policy_loss.item(),
"value_loss": value_loss.item(),
"kl_penalty": kl_penalty if isinstance(kl_penalty, float) else kl_penalty.item(),
"mean_ratio": ratio.mean().item(),
}
PPO 학습의 주요 불안정성 원인
- 보상 해킹(Reward Hacking): 보상 모델의 취약점을 악용하여 실제로 좋지 않은 출력에 높은 보상을 받는 현상
- KL 발산 폭발: 참조 모델과의 거리가 급격히 증가하여 의미 없는 텍스트 생성
- 가치 함수 추정 오류: 비정상적으로 큰 보상 추정치가 학습을 불안정하게 만듦
- 긴 시퀀스 문제: 토큰 수가 많을수록 크레딧 할당(credit assignment) 문제가 심화
최신 정렬 기법: KTO, IPO, ORPO
KTO (Kahneman-Tversky Optimization)
Ethayarajh et al.(2024)의 KTO는 쌍별 선호 데이터 없이 정렬을 수행한다. 행동경제학의 전망 이론(Prospect Theory)을 차용하여, 각 응답이 "바람직한가/바람직하지 않은가"의 이진 신호만으로 학습한다.
KTO의 핵심은 인간의 의사결정이 손실에 더 민감하다는 손실 회피(Loss Aversion) 특성을 반영한 것이다. 바람직하지 않은 응답에 대한 패널티를 바람직한 응답에 대한 보상보다 더 크게 설정한다.
IPO (Identity Preference Optimization)
Azar et al.(2024)의 IPO는 DPO의 과적합(overfitting) 문제를 해결하기 위해 정규화 항을 추가한다. DPO는 학습 데이터에 과적합되면 선호 응답의 확률을 극단적으로 높이는 경향이 있는데, IPO는 이를 방지한다.
ORPO (Odds Ratio Preference Optimization)
Hong et al.(2024)의 ORPO는 SFT와 선호 최적화를 단일 단계로 통합한다. 참조 모델이 불필요하여 메모리와 계산 비용을 크게 절감한다. 핵심은 승산비(Odds Ratio) 기반 패널티 항이다.
import torch
import torch.nn.functional as F
def compute_orpo_loss(model, chosen_ids, chosen_mask, rejected_ids, rejected_mask, alpha=1.0):
"""ORPO 손실: SFT + Odds Ratio 기반 선호 최적화 통합"""
# SFT 손실 (선호 응답에 대한 NLL)
chosen_logits = model(input_ids=chosen_ids, attention_mask=chosen_mask).logits
sft_loss = F.cross_entropy(
chosen_logits[:, :-1, :].reshape(-1, chosen_logits.size(-1)),
chosen_ids[:, 1:].reshape(-1),
reduction="mean",
)
# 로그 확률 계산
chosen_logps = get_sequence_log_probs(model, chosen_ids, chosen_mask)
rejected_logps = get_sequence_log_probs(model, rejected_ids, rejected_mask)
# 승산비(Odds Ratio) 계산
chosen_odds = chosen_logps - torch.log1p(-torch.exp(chosen_logps))
rejected_odds = rejected_logps - torch.log1p(-torch.exp(rejected_logps))
log_odds_ratio = chosen_odds - rejected_odds
# ORPO 최종 손실
orpo_loss = sft_loss - alpha * F.logsigmoid(log_odds_ratio).mean()
return orpo_loss
비교 분석
각 정렬 기법의 핵심 특성을 비교하면 다음과 같다.
| 기법 | 데이터 요구사항 | 모델 수 | 계산 비용 | 안정성 | 성능 |
|---|---|---|---|---|---|
| RLHF (PPO) | 쌍별 선호 | 4 (정책, 참조, 보상, 가치) | 매우 높음 | 낮음 (튜닝 필요) | 높음 (최적 튜닝 시) |
| DPO | 쌍별 선호 | 2 (정책, 참조) | 중간 | 높음 | 높음 |
| KTO | 이진 피드백 (비쌍별) | 2 (정책, 참조) | 중간 | 높음 | 중상 |
| IPO | 쌍별 선호 | 2 (정책, 참조) | 중간 | 매우 높음 | 중상 |
| ORPO | 쌍별 선호 | 1 (정책만) | 낮음 | 높음 | 중상 |
핵심 트레이드오프:
- RLHF: 최고 성능 잠재력이 있지만, 학습 불안정성과 엔지니어링 복잡도가 높다
- DPO: 구현 단순성과 성능의 균형이 뛰어나다. 현재 가장 널리 채택된 기법
- KTO: 쌍별 데이터 구축이 어려운 환경에서 강력한 대안
- IPO: DPO의 과적합 문제가 심한 소규모 데이터셋에 적합
- ORPO: 계산 자원이 제한된 환경에서 효율적인 선택
실무 적용 가이드
기법 선택 기준
실무에서 정렬 기법을 선택할 때는 다음 기준을 고려해야 한다.
- 데이터 형태: 쌍별 선호 데이터가 있는가? 이진 피드백만 가능한가?
- 계산 자원: GPU 메모리와 학습 시간의 제약은?
- 팀 역량: PPO 튜닝 경험이 있는가?
- 모델 크기: 7B 이하라면 DPO, 70B 이상이라면 RLHF 검토
평가 메트릭 구현
정렬 결과를 평가하기 위한 핵심 메트릭은 다음과 같다.
import numpy as np
from typing import List, Dict
def compute_alignment_metrics(
model_responses: List[str],
reference_responses: List[str],
reward_model,
tokenizer,
) -> Dict[str, float]:
"""정렬 품질 평가를 위한 종합 메트릭 계산"""
metrics = {}
# 1. 보상 모델 점수
rewards = []
for response in model_responses:
tokens = tokenizer(response, return_tensors="pt")
score = reward_model(**tokens).item()
rewards.append(score)
metrics["mean_reward"] = np.mean(rewards)
metrics["reward_std"] = np.std(rewards)
# 2. 승률 (win rate) - 참조 모델 대비
wins = 0
for model_resp, ref_resp in zip(model_responses, reference_responses):
model_tokens = tokenizer(model_resp, return_tensors="pt")
ref_tokens = tokenizer(ref_resp, return_tensors="pt")
model_score = reward_model(**model_tokens).item()
ref_score = reward_model(**ref_tokens).item()
if model_score > ref_score:
wins += 1
metrics["win_rate"] = wins / len(model_responses)
# 3. 응답 길이 분포 (길이 해킹 탐지)
lengths = [len(r.split()) for r in model_responses]
metrics["mean_length"] = np.mean(lengths)
metrics["length_std"] = np.std(lengths)
# 4. 다양성 지표 (모드 붕괴 탐지)
unique_bigrams = set()
total_bigrams = 0
for response in model_responses:
words = response.split()
for i in range(len(words) - 1):
bigram = (words[i], words[i + 1])
unique_bigrams.add(bigram)
total_bigrams += 1
metrics["distinct_2"] = len(unique_bigrams) / max(total_bigrams, 1)
return metrics
실패 사례와 교훈
1. 보상 해킹 (Reward Hacking)
RLHF에서 가장 흔한 실패 모드이다. 보상 모델이 "긴 응답 = 좋은 응답"이라는 허위 상관관계를 학습하면, 정책 모델은 내용과 무관하게 지나치게 긴 응답을 생성한다. InstructGPT 논문에서도 이 문제를 보고했으며, 길이 정규화(length normalization)와 KL 페널티 강화로 부분적으로 해결했다.
교훈: 보상 모델의 편향을 지속적으로 모니터링하고, 다양한 프록시 메트릭으로 교차 검증해야 한다.
2. 모드 붕괴 (Mode Collapse)
PPO 학습 중 정책이 보상 모델에서 높은 점수를 받는 소수의 패턴만 반복 생성하는 현상이다. 다양한 질문에 동일한 구조의 응답을 출력하거나, 특정 표현을 과도하게 반복한다.
교훈: KL 발산 제약을 적절히 유지하고, 응답 다양성 메트릭을 학습 중 모니터링해야 한다.
3. DPO의 분포 이동 (Distribution Shift)
DPO는 오프라인 선호 데이터로 학습하므로, 학습이 진행될수록 현재 정책이 생성하는 분포와 학습 데이터의 분포가 괴리된다. 이로 인해 학습 후반부에 성능이 저하될 수 있다.
교훈: 반복적(iterative) DPO를 적용하여 매 라운드마다 현재 정책으로 새로운 선호 데이터를 생성하거나, 온라인 DPO 변형을 사용해야 한다.
4. Constitutional AI의 원칙 충돌
헌법에 포함된 원칙들이 서로 충돌하는 경우가 발생한다. 예를 들어 "도움이 되어라"와 "유해한 정보를 제공하지 마라"는 원칙이 특정 맥락에서 상충할 수 있다.
교훈: 원칙 간 우선순위를 명확히 정의하고, 경계 사례(edge cases)에 대한 별도의 가이드라인을 마련해야 한다.
참고자료
Ouyang, L., et al. (2022). "Training language models to follow instructions with human feedback." NeurIPS 2022. https://arxiv.org/abs/2203.02155
Rafailov, R., et al. (2023). "Direct Preference Optimization: Your Language Model is Secretly a Reward Model." NeurIPS 2023. https://arxiv.org/abs/2305.18290
Bai, Y., et al. (2022). "Constitutional AI: Harmlessness from AI Feedback." Anthropic. https://arxiv.org/abs/2212.08073
Ethayarajh, K., et al. (2024). "KTO: Model Alignment as Prospect Theoretic Optimization." https://arxiv.org/abs/2402.01306
Hong, J., et al. (2024). "ORPO: Monolithic Preference Optimization without Reference Model." https://arxiv.org/abs/2403.07691
Azar, M. G., et al. (2024). "A General Theoretical Paradigm to Understand Learning from Human Feedback." (IPO) https://arxiv.org/abs/2310.12036
Schulman, J., et al. (2017). "Proximal Policy Optimization Algorithms." https://arxiv.org/abs/1707.06347
Hugging Face Blog. "Preference Tuning LLMs with Direct Preference Optimization Methods." https://huggingface.co/blog/pref-tuning
From RLHF to DPO: A Deep Dive into LLM Alignment Techniques
- Introduction
- Defining the Alignment Problem
- Deep Analysis of the RLHF Pipeline
- Constitutional AI: Alignment from AI Feedback
- DPO: Mathematical Foundations and Implementation
- PPO Training Stability
- Recent Alignment Methods: KTO, IPO, ORPO
- Comparative Analysis
- Practical Application Guide
- Failure Cases and Lessons Learned
- References

Introduction
Large language models (LLMs) acquire remarkable linguistic capabilities from massive pretraining corpora, but pretraining alone cannot guarantee outputs aligned with human intentions and values. Models frequently generate harmful content, ignore user instructions, or confidently state falsehoods. The research field dedicated to bridging this gap is LLM Alignment.
Since OpenAI's InstructGPT paper (2022) formalized the RLHF (Reinforcement Learning from Human Feedback) pipeline, numerous successors have emerged: Anthropic's Constitutional AI, Stanford's DPO (Direct Preference Optimization), and newer methods like KTO, IPO, and ORPO. This article systematically analyzes the mathematical foundations, implementation details, and practical considerations of these key alignment techniques.
Defining the Alignment Problem
Why Alignment Matters
Pretrained LLMs optimize next-token prediction over internet text. This objective does not directly correspond to generating outputs that are "Helpful, Honest, and Harmless" (the HHH criteria). Three core challenges arise:
- Helpfulness: The ability to accurately understand and follow user instructions
- Honesty: Acknowledging uncertainty and minimizing hallucinations
- Harmlessness: Refusing to generate harmful or biased content
Mathematical Framework
The alignment problem is formalized as a reward-constrained policy optimization. Given a prompt x, we maximize the reward for responses y generated by policy pi, while constraining divergence from a pretrained reference policy.
Here, beta controls the strength of the KL divergence penalty. If beta is too small, reward hacking occurs; if too large, learning stalls.
Deep Analysis of the RLHF Pipeline
InstructGPT's Three-Stage Pipeline
Ouyang et al. (2022) formalized RLHF into three stages:
Stage 1: Supervised Fine-Tuning (SFT)
Human labelers write demonstration data showing desired model behavior. The base model is fine-tuned on these demonstrations, learning basic instruction-following capability.
Stage 2: Reward Model (RM) Training
Multiple responses are generated for the same prompt, then human labelers rank them by preference. A reward model is trained on this ranking data.
Stage 3: Reinforcement Learning via PPO
The reward model provides feedback signals for PPO to optimize the policy.
Reward Model Training Implementation
The reward model is trained using the Bradley-Terry preference model, maximizing the log-probability difference between chosen and rejected response pairs.
import torch
import torch.nn as nn
from transformers import AutoModelForSequenceClassification, AutoTokenizer
class RewardModel(nn.Module):
def __init__(self, model_name="gpt2"):
super().__init__()
self.model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=1
)
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
return outputs.logits.squeeze(-1)
def reward_model_loss(reward_model, chosen_ids, chosen_mask, rejected_ids, rejected_mask):
"""Bradley-Terry preference model loss for reward model training"""
r_chosen = reward_model(chosen_ids, chosen_mask)
r_rejected = reward_model(rejected_ids, rejected_mask)
# Train the reward for chosen responses to exceed rejected responses
loss = -torch.log(torch.sigmoid(r_chosen - r_rejected)).mean()
accuracy = (r_chosen > r_rejected).float().mean()
return loss, accuracy
Preference Dataset Construction
The cornerstone of the RLHF pipeline is high-quality preference data. Each sample consists of a prompt, a preferred (chosen) response, and a dispreferred (rejected) response.
from datasets import Dataset
def build_preference_dataset(prompts, model, tokenizer, num_samples=4):
"""Pipeline to generate multiple responses per prompt and construct preference pairs"""
preference_data = []
for prompt in prompts:
inputs = tokenizer(prompt, return_tensors="pt")
responses = []
# Generate diverse responses via temperature sampling
for _ in range(num_samples):
output = model.generate(
**inputs,
max_new_tokens=256,
temperature=0.8,
do_sample=True,
)
response = tokenizer.decode(output[0], skip_special_tokens=True)
responses.append(response)
# In production, human labelers rank the responses
# Here we substitute reward model scores
scored = [(r, reward_model_score(prompt, r)) for r in responses]
scored.sort(key=lambda x: x[1], reverse=True)
# Construct preference pairs from best and worst
preference_data.append({
"prompt": prompt,
"chosen": scored[0][0],
"rejected": scored[-1][0],
})
return Dataset.from_list(preference_data)
Constitutional AI: Alignment from AI Feedback
Anthropic's Approach
Bai et al. (2022) introduced Constitutional AI to reduce RLHF's dependence on human labeling. The core idea is that AI critiques and improves its own outputs, guided by a set of predefined principles (the "constitution").
Two-Phase Process
Supervised Learning Phase (Self-Critique and Revision):
- The model generates a potentially harmful response
- It critiques its own response based on constitutional principles
- It generates a revised response reflecting the critique
- SFT is performed on the revised responses
RL Phase (RLAIF - RL from AI Feedback):
- The AI judges which of two responses better adheres to the principles
- A reward model is trained on these AI preference labels
- PPO optimizes the policy using this reward model
The greatest advantage of Constitutional AI is dramatically reducing human labeling costs while achieving transparent, interpretable, principle-based alignment.
DPO: Mathematical Foundations and Implementation
The Analytical Solution to RLHF
Rafailov et al. (2023) introduced DPO as the most elegant alternative to RLHF. The key insight is that the KL-constrained reward maximization problem has an analytical solution.
The optimal policy takes the form:
Inverting this, the reward function can be expressed as a ratio of policies:
Substituting into the Bradley-Terry model yields the DPO loss, which directly optimizes the policy without a reward model:
DPO Training Loop Implementation
import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
def compute_dpo_loss(
policy_model,
reference_model,
chosen_ids,
chosen_mask,
rejected_ids,
rejected_mask,
beta=0.1,
):
"""DPO loss function: direct policy optimization without a reward model"""
# Compute log probabilities under the policy model
policy_chosen_logps = get_log_probs(policy_model, chosen_ids, chosen_mask)
policy_rejected_logps = get_log_probs(policy_model, rejected_ids, rejected_mask)
# Compute log probabilities under the reference model (no gradients needed)
with torch.no_grad():
ref_chosen_logps = get_log_probs(reference_model, chosen_ids, chosen_mask)
ref_rejected_logps = get_log_probs(reference_model, rejected_ids, rejected_mask)
# Compute DPO log ratios
chosen_logratios = policy_chosen_logps - ref_chosen_logps
rejected_logratios = policy_rejected_logps - ref_rejected_logps
# DPO loss: -log(sigmoid(beta * (chosen_logratio - rejected_logratio)))
logits = beta * (chosen_logratios - rejected_logratios)
loss = -F.logsigmoid(logits).mean()
# Metric: fraction where implicit reward for chosen exceeds rejected
reward_accuracy = (logits > 0).float().mean()
return loss, reward_accuracy
def get_log_probs(model, input_ids, attention_mask):
"""Sum token-level log probabilities over the sequence"""
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
logits = outputs.logits[:, :-1, :]
labels = input_ids[:, 1:]
log_probs = F.log_softmax(logits, dim=-1)
selected_log_probs = torch.gather(
log_probs, dim=-1, index=labels.unsqueeze(-1)
).squeeze(-1)
mask = attention_mask[:, 1:]
return (selected_log_probs * mask).sum(dim=-1)
Strengths and Weaknesses of DPO
Strengths:
- No reward model training needed, simplifying the pipeline
- Lower memory footprint than PPO (2 models vs. 4 models)
- Relatively straightforward hyperparameter tuning
Weaknesses:
- Vulnerable to distribution shift from the reference model
- Relies on offline data, lacking exploration
- Highly sensitive to preference data quality
PPO Training Stability
PPO in RLHF: Core Implementation
Proximal Policy Optimization (PPO) is the most widely used RL algorithm in RLHF. Its core mechanism is clipping, which constrains the magnitude of policy updates to ensure training stability.
import torch
import torch.nn.functional as F
def ppo_step(
policy_model,
value_model,
old_log_probs,
states,
actions,
rewards,
advantages,
clip_epsilon=0.2,
value_clip=0.2,
kl_coeff=0.1,
ref_log_probs=None,
):
"""PPO update step for LLM RLHF training"""
# Current policy log probabilities
new_log_probs = policy_model.get_log_probs(states, actions)
# Importance sampling ratio
ratio = torch.exp(new_log_probs - old_log_probs)
# Clipped surrogate objective
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1.0 - clip_epsilon, 1.0 + clip_epsilon) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
# Value function loss (with clipping)
new_values = value_model(states)
value_loss = F.mse_loss(new_values, rewards)
# KL divergence penalty (against reference model)
kl_penalty = 0.0
if ref_log_probs is not None:
kl_div = (old_log_probs - ref_log_probs).mean()
kl_penalty = kl_coeff * kl_div
total_loss = policy_loss + 0.5 * value_loss + kl_penalty
return total_loss, {
"policy_loss": policy_loss.item(),
"value_loss": value_loss.item(),
"kl_penalty": kl_penalty if isinstance(kl_penalty, float) else kl_penalty.item(),
"mean_ratio": ratio.mean().item(),
}
Key Sources of PPO Instability
- Reward Hacking: Exploiting reward model weaknesses to achieve high reward for genuinely poor outputs
- KL Divergence Explosion: Rapid divergence from the reference model, resulting in degenerate text
- Value Function Estimation Errors: Abnormally large reward estimates destabilizing training
- Long Sequence Problem: Credit assignment difficulty increases with token count
Recent Alignment Methods: KTO, IPO, ORPO
KTO (Kahneman-Tversky Optimization)
Ethayarajh et al. (2024) introduced KTO, which performs alignment without pairwise preference data. Drawing from behavioral economics' Prospect Theory, it learns from binary signals indicating whether each response is "desirable" or "undesirable."
KTO's key innovation is incorporating loss aversion: the penalty for undesirable responses is larger than the reward for desirable ones, mirroring how humans weigh losses more heavily than gains.
IPO (Identity Preference Optimization)
Azar et al. (2024) introduced IPO to address DPO's overfitting problem by adding a regularization term. When DPO overfits, it tends to push chosen response probabilities to extremes; IPO prevents this.
ORPO (Odds Ratio Preference Optimization)
Hong et al. (2024) introduced ORPO, which unifies SFT and preference optimization into a single stage. It requires no reference model, significantly reducing memory and compute costs. The key is an odds-ratio-based penalty term.
import torch
import torch.nn.functional as F
def compute_orpo_loss(model, chosen_ids, chosen_mask, rejected_ids, rejected_mask, alpha=1.0):
"""ORPO loss: unified SFT + odds-ratio preference optimization"""
# SFT loss (NLL on chosen responses)
chosen_logits = model(input_ids=chosen_ids, attention_mask=chosen_mask).logits
sft_loss = F.cross_entropy(
chosen_logits[:, :-1, :].reshape(-1, chosen_logits.size(-1)),
chosen_ids[:, 1:].reshape(-1),
reduction="mean",
)
# Compute log probabilities
chosen_logps = get_sequence_log_probs(model, chosen_ids, chosen_mask)
rejected_logps = get_sequence_log_probs(model, rejected_ids, rejected_mask)
# Odds ratio computation
chosen_odds = chosen_logps - torch.log1p(-torch.exp(chosen_logps))
rejected_odds = rejected_logps - torch.log1p(-torch.exp(rejected_logps))
log_odds_ratio = chosen_odds - rejected_odds
# Final ORPO loss
orpo_loss = sft_loss - alpha * F.logsigmoid(log_odds_ratio).mean()
return orpo_loss
Comparative Analysis
A comparison of the key characteristics of each alignment technique:
| Method | Data Requirements | Num Models | Compute Cost | Stability | Performance |
|---|---|---|---|---|---|
| RLHF (PPO) | Pairwise prefs | 4 (policy, ref, reward, value) | Very High | Low (tuning needed) | High (when well-tuned) |
| DPO | Pairwise prefs | 2 (policy, ref) | Medium | High | High |
| KTO | Binary feedback (unpaired) | 2 (policy, ref) | Medium | High | Medium-High |
| IPO | Pairwise prefs | 2 (policy, ref) | Medium | Very High | Medium-High |
| ORPO | Pairwise prefs | 1 (policy only) | Low | High | Medium-High |
Key Trade-offs:
- RLHF: Highest performance ceiling, but high instability and engineering complexity
- DPO: Best balance of simplicity and performance. Currently the most widely adopted method
- KTO: Strong alternative when constructing pairwise data is difficult
- IPO: Best suited for small datasets where DPO overfitting is severe
- ORPO: Efficient choice when compute resources are limited
Practical Application Guide
Selection Criteria
When choosing an alignment technique in practice, consider:
- Data format: Do you have pairwise preference data, or only binary feedback?
- Compute resources: What are the GPU memory and training time constraints?
- Team expertise: Does your team have PPO tuning experience?
- Model size: For 7B and below, DPO is recommended; for 70B+, consider RLHF
Evaluation Metrics Implementation
Key metrics for evaluating alignment quality:
import numpy as np
from typing import List, Dict
def compute_alignment_metrics(
model_responses: List[str],
reference_responses: List[str],
reward_model,
tokenizer,
) -> Dict[str, float]:
"""Comprehensive metrics for alignment quality evaluation"""
metrics = {}
# 1. Reward model scores
rewards = []
for response in model_responses:
tokens = tokenizer(response, return_tensors="pt")
score = reward_model(**tokens).item()
rewards.append(score)
metrics["mean_reward"] = np.mean(rewards)
metrics["reward_std"] = np.std(rewards)
# 2. Win rate against reference model
wins = 0
for model_resp, ref_resp in zip(model_responses, reference_responses):
model_tokens = tokenizer(model_resp, return_tensors="pt")
ref_tokens = tokenizer(ref_resp, return_tensors="pt")
model_score = reward_model(**model_tokens).item()
ref_score = reward_model(**ref_tokens).item()
if model_score > ref_score:
wins += 1
metrics["win_rate"] = wins / len(model_responses)
# 3. Response length distribution (length hacking detection)
lengths = [len(r.split()) for r in model_responses]
metrics["mean_length"] = np.mean(lengths)
metrics["length_std"] = np.std(lengths)
# 4. Diversity metric (mode collapse detection)
unique_bigrams = set()
total_bigrams = 0
for response in model_responses:
words = response.split()
for i in range(len(words) - 1):
bigram = (words[i], words[i + 1])
unique_bigrams.add(bigram)
total_bigrams += 1
metrics["distinct_2"] = len(unique_bigrams) / max(total_bigrams, 1)
return metrics
Failure Cases and Lessons Learned
1. Reward Hacking
The most common failure mode in RLHF. When the reward model learns spurious correlations like "longer response = better response," the policy generates excessively long outputs regardless of content. The InstructGPT paper reported this issue and partially addressed it with length normalization and stronger KL penalties.
Lesson: Continuously monitor reward model biases and cross-validate with diverse proxy metrics.
2. Mode Collapse
During PPO training, the policy may converge to generating only a few patterns that score highly with the reward model. It produces identically structured responses for diverse questions or repeats specific phrases excessively.
Lesson: Maintain appropriate KL divergence constraints and monitor response diversity metrics during training.
3. DPO Distribution Shift
Since DPO trains on offline preference data, the distribution of the current policy progressively diverges from the training data distribution as learning proceeds. This can cause performance degradation in later stages of training.
Lesson: Apply iterative DPO, generating new preference data from the current policy each round, or use online DPO variants.
4. Constitutional AI Principle Conflicts
Principles included in the constitution can conflict with one another. For example, "be helpful" and "do not provide harmful information" may clash in certain contexts.
Lesson: Clearly define priority orderings among principles and establish separate guidelines for edge cases.
References
Ouyang, L., et al. (2022). "Training language models to follow instructions with human feedback." NeurIPS 2022. https://arxiv.org/abs/2203.02155
Rafailov, R., et al. (2023). "Direct Preference Optimization: Your Language Model is Secretly a Reward Model." NeurIPS 2023. https://arxiv.org/abs/2305.18290
Bai, Y., et al. (2022). "Constitutional AI: Harmlessness from AI Feedback." Anthropic. https://arxiv.org/abs/2212.08073
Ethayarajh, K., et al. (2024). "KTO: Model Alignment as Prospect Theoretic Optimization." https://arxiv.org/abs/2402.01306
Hong, J., et al. (2024). "ORPO: Monolithic Preference Optimization without Reference Model." https://arxiv.org/abs/2403.07691
Azar, M. G., et al. (2024). "A General Theoretical Paradigm to Understand Learning from Human Feedback." (IPO) https://arxiv.org/abs/2310.12036
Schulman, J., et al. (2017). "Proximal Policy Optimization Algorithms." https://arxiv.org/abs/1707.06347
Hugging Face Blog. "Preference Tuning LLMs with Direct Preference Optimization Methods." https://huggingface.co/blog/pref-tuning