Skip to content

Split View: RLHF에서 DPO까지: LLM 정렬(Alignment) 기술 논문 심층 분석

|

RLHF에서 DPO까지: LLM 정렬(Alignment) 기술 논문 심층 분석

LLM Alignment Survey

들어가며

대규모 언어 모델(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)" 출력과 직접적으로 대응하지 않는다. 구체적으로 세 가지 핵심 문제가 있다.

  1. 유용성(Helpfulness): 사용자의 지시를 정확히 이해하고 수행하는 능력
  2. 정직성(Honesty): 모르는 것을 모른다고 인정하고, 환각을 최소화하는 능력
  3. 무해성(Harmlessness): 유해하거나 편향된 콘텐츠 생성을 거부하는 능력

정렬의 수학적 프레임워크

정렬 문제는 보상 함수 r(x, y)를 기반으로 정책 최적화 문제로 형식화된다. 프롬프트 x가 주어졌을 때, 정책 pi가 생성하는 응답 y에 대해 보상을 최대화하되, 사전학습된 참조 정책과의 괴리를 제한한다.

maxπExD,yπ(x)[r(x,y)]βKL[π(x)πref(x)]\max_{\pi} \mathbb{E}_{x \sim \mathcal{D}, y \sim \pi(\cdot|x)} \left[ r(x, y) \right] - \beta \, \text{KL}\left[\pi(\cdot|x) \| \pi_{\text{ref}}(\cdot|x)\right]

여기서 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 단계 (자기 비판 및 수정):

  1. 모델이 유해할 수 있는 응답을 생성한다
  2. 헌법 원칙에 따라 자신의 응답을 비판한다
  3. 비판을 반영하여 수정된 응답을 생성한다
  4. 수정된 응답으로 SFT를 수행한다

RL 단계 (RLAIF - RL from AI Feedback):

  1. 두 응답 중 어느 것이 원칙에 더 부합하는지 AI가 판단한다
  2. 이 AI 선호 데이터로 보상 모델을 학습한다
  3. 보상 모델을 이용하여 PPO로 정책을 최적화한다

Constitutional AI의 가장 큰 장점은 인간 레이블링 비용을 대폭 절감하면서도, 원칙 기반의 투명하고 해석 가능한 정렬을 달성한다는 점이다.

DPO: 수학적 기반과 구현

RLHF 목적함수의 해석적 해

Rafailov et al.(2023)의 DPO는 RLHF의 가장 우아한 대안이다. 핵심 통찰은 KL 제약이 있는 보상 최대화 문제에 해석적 해가 존재한다는 것이다.

최적 정책은 다음과 같은 형태를 갖는다.

π(yx)=1Z(x)πref(yx)exp(1βr(x,y))\pi^*(y|x) = \frac{1}{Z(x)} \pi_{\text{ref}}(y|x) \exp\left(\frac{1}{\beta} r(x, y)\right)

이를 역으로 풀면, 보상 함수를 정책의 비율로 표현할 수 있다.

r(x,y)=βlogπ(yx)πref(yx)+βlogZ(x)r(x, y) = \beta \log \frac{\pi^*(y|x)}{\pi_{\text{ref}}(y|x)} + \beta \log Z(x)

이를 Bradley-Terry 모델에 대입하면, 보상 모델 없이 직접 정책을 최적화하는 DPO 손실함수가 도출된다.

LDPO(πθ;πref)=E(x,yw,yl)[logσ(βlogπθ(ywx)πref(ywx)βlogπθ(ylx)πref(ylx))]\mathcal{L}_{\text{DPO}}(\pi_\theta; \pi_{\text{ref}}) = -\mathbb{E}_{(x, y_w, y_l)} \left[\log \sigma\left(\beta \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\right)\right]

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 학습의 주요 불안정성 원인

  1. 보상 해킹(Reward Hacking): 보상 모델의 취약점을 악용하여 실제로 좋지 않은 출력에 높은 보상을 받는 현상
  2. KL 발산 폭발: 참조 모델과의 거리가 급격히 증가하여 의미 없는 텍스트 생성
  3. 가치 함수 추정 오류: 비정상적으로 큰 보상 추정치가 학습을 불안정하게 만듦
  4. 긴 시퀀스 문제: 토큰 수가 많을수록 크레딧 할당(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는 이를 방지한다.

LIPO=(logπθ(ywx)πref(ywx)logπθ(ylx)πref(ylx)12β)2\mathcal{L}_{\text{IPO}} = \left(\log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} - \frac{1}{2\beta}\right)^2

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: 계산 자원이 제한된 환경에서 효율적인 선택

실무 적용 가이드

기법 선택 기준

실무에서 정렬 기법을 선택할 때는 다음 기준을 고려해야 한다.

  1. 데이터 형태: 쌍별 선호 데이터가 있는가? 이진 피드백만 가능한가?
  2. 계산 자원: GPU 메모리와 학습 시간의 제약은?
  3. 팀 역량: PPO 튜닝 경험이 있는가?
  4. 모델 크기: 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)에 대한 별도의 가이드라인을 마련해야 한다.

참고자료

  1. Ouyang, L., et al. (2022). "Training language models to follow instructions with human feedback." NeurIPS 2022. https://arxiv.org/abs/2203.02155

  2. Rafailov, R., et al. (2023). "Direct Preference Optimization: Your Language Model is Secretly a Reward Model." NeurIPS 2023. https://arxiv.org/abs/2305.18290

  3. Bai, Y., et al. (2022). "Constitutional AI: Harmlessness from AI Feedback." Anthropic. https://arxiv.org/abs/2212.08073

  4. Ethayarajh, K., et al. (2024). "KTO: Model Alignment as Prospect Theoretic Optimization." https://arxiv.org/abs/2402.01306

  5. Hong, J., et al. (2024). "ORPO: Monolithic Preference Optimization without Reference Model." https://arxiv.org/abs/2403.07691

  6. Azar, M. G., et al. (2024). "A General Theoretical Paradigm to Understand Learning from Human Feedback." (IPO) https://arxiv.org/abs/2310.12036

  7. Schulman, J., et al. (2017). "Proximal Policy Optimization Algorithms." https://arxiv.org/abs/1707.06347

  8. 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

LLM Alignment Survey

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:

  1. Helpfulness: The ability to accurately understand and follow user instructions
  2. Honesty: Acknowledging uncertainty and minimizing hallucinations
  3. 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.

maxπExD,yπ(x)[r(x,y)]βKL[π(x)πref(x)]\max_{\pi} \mathbb{E}_{x \sim \mathcal{D}, y \sim \pi(\cdot|x)} \left[ r(x, y) \right] - \beta \, \text{KL}\left[\pi(\cdot|x) \| \pi_{\text{ref}}(\cdot|x)\right]

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):

  1. The model generates a potentially harmful response
  2. It critiques its own response based on constitutional principles
  3. It generates a revised response reflecting the critique
  4. SFT is performed on the revised responses

RL Phase (RLAIF - RL from AI Feedback):

  1. The AI judges which of two responses better adheres to the principles
  2. A reward model is trained on these AI preference labels
  3. 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:

π(yx)=1Z(x)πref(yx)exp(1βr(x,y))\pi^*(y|x) = \frac{1}{Z(x)} \pi_{\text{ref}}(y|x) \exp\left(\frac{1}{\beta} r(x, y)\right)

Inverting this, the reward function can be expressed as a ratio of policies:

r(x,y)=βlogπ(yx)πref(yx)+βlogZ(x)r(x, y) = \beta \log \frac{\pi^*(y|x)}{\pi_{\text{ref}}(y|x)} + \beta \log Z(x)

Substituting into the Bradley-Terry model yields the DPO loss, which directly optimizes the policy without a reward model:

LDPO(πθ;πref)=E(x,yw,yl)[logσ(βlogπθ(ywx)πref(ywx)βlogπθ(ylx)πref(ylx))]\mathcal{L}_{\text{DPO}}(\pi_\theta; \pi_{\text{ref}}) = -\mathbb{E}_{(x, y_w, y_l)} \left[\log \sigma\left(\beta \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\right)\right]

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

  1. Reward Hacking: Exploiting reward model weaknesses to achieve high reward for genuinely poor outputs
  2. KL Divergence Explosion: Rapid divergence from the reference model, resulting in degenerate text
  3. Value Function Estimation Errors: Abnormally large reward estimates destabilizing training
  4. 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.

LIPO=(logπθ(ywx)πref(ywx)logπθ(ylx)πref(ylx)12β)2\mathcal{L}_{\text{IPO}} = \left(\log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)} - \frac{1}{2\beta}\right)^2

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:

MethodData RequirementsNum ModelsCompute CostStabilityPerformance
RLHF (PPO)Pairwise prefs4 (policy, ref, reward, value)Very HighLow (tuning needed)High (when well-tuned)
DPOPairwise prefs2 (policy, ref)MediumHighHigh
KTOBinary feedback (unpaired)2 (policy, ref)MediumHighMedium-High
IPOPairwise prefs2 (policy, ref)MediumVery HighMedium-High
ORPOPairwise prefs1 (policy only)LowHighMedium-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:

  1. Data format: Do you have pairwise preference data, or only binary feedback?
  2. Compute resources: What are the GPU memory and training time constraints?
  3. Team expertise: Does your team have PPO tuning experience?
  4. 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

  1. Ouyang, L., et al. (2022). "Training language models to follow instructions with human feedback." NeurIPS 2022. https://arxiv.org/abs/2203.02155

  2. Rafailov, R., et al. (2023). "Direct Preference Optimization: Your Language Model is Secretly a Reward Model." NeurIPS 2023. https://arxiv.org/abs/2305.18290

  3. Bai, Y., et al. (2022). "Constitutional AI: Harmlessness from AI Feedback." Anthropic. https://arxiv.org/abs/2212.08073

  4. Ethayarajh, K., et al. (2024). "KTO: Model Alignment as Prospect Theoretic Optimization." https://arxiv.org/abs/2402.01306

  5. Hong, J., et al. (2024). "ORPO: Monolithic Preference Optimization without Reference Model." https://arxiv.org/abs/2403.07691

  6. Azar, M. G., et al. (2024). "A General Theoretical Paradigm to Understand Learning from Human Feedback." (IPO) https://arxiv.org/abs/2310.12036

  7. Schulman, J., et al. (2017). "Proximal Policy Optimization Algorithms." https://arxiv.org/abs/1707.06347

  8. Hugging Face Blog. "Preference Tuning LLMs with Direct Preference Optimization Methods." https://huggingface.co/blog/pref-tuning