Navigation

Introduction to AI

Machine Learning

Deep Learning

Generative AI

Tools & Frameworks

General

RLHF: Reinforcement Learning from Human Feedback

Reinforcement Learning from Human Feedback (RLHF) is the technique used to align large language models with human values and preferences. It powered models like ChatGPT, Claude, and GPT-4, enabling them to follow instructions and respond helpfully.

The Three-Stage RLHF Pipeline

Stage 1: Supervised Fine-Tuning (SFT)

Collect demonstrations of desired behavior and fine-tune the base model:

# After SFT, the model can follow instructions
# but may still produce problematic outputs

Stage 2: Reward Model Training

Collect human comparisons of model outputs, train a reward model:

import torch
import torch.nn as nn

class RewardModel(nn.Module):
    def __init__(self, base_model):
        super().__init__()
        self.base_model = base_model
        # Replace final layer with a scalar head
        self.reward_head = nn.Linear(base_model.config.hidden_size, 1)
    
    def forward(self, input_ids):
        # Get hidden states
        outputs = self.base_model(input_ids)
        # Use [CLS] token representation
        reward = self.reward_head(outputs.last_hidden_state[:, 0])
        return reward.squeeze(-1)

# Training: maximize margin between preferred and rejected responses
def compute_reward_loss(chosen_rewards, rejected_rewards):
    """Pairwise ranking loss for reward model."""
    loss = torch.nn.functional.relu(
        rejected_rewards - chosen_rewards + margin
    )
    return loss.mean()

Stage 3: Policy Optimization (PPO)

Use the reward model to fine-tune the SFT model with RL:

import torch.optim as optim
from collections import deque

class PPOTrainer:
    def __init__(self, policy_model, ref_model, reward_model, clip_epsilon=0.2):
        self.policy = policy_model
        self.ref = ref_model  # Reference model (SFT) to prevent drift
        self.reward = reward_model
        self.clip = clip_epsilon
        self.optimizer = optim.Adam(self.policy.parameters(), lr=1e-5)
    
    def compute_advantages(self, rewards, values, dones, gamma=0.99, lam=0.95):
        """Generalized Advantage Estimation."""
        advantages = []
        gae = 0
        
        for t in reversed(range(len(rewards))):
            delta = rewards[t] + gamma * values[t+1] * (1 - dones[t]) - values[t]
            gae = delta + gamma * lam * (1 - dones[t]) * gae
            advantages.insert(0, gae)
        
        return advantages
    
    def ppo_update(self, states, actions, old_log_probs, rewards, dones):
        """PPO clip objective update."""
        # Get new policy outputs
        logits = self.policy(states)
        new_log_probs = log_softmax(logits).gather(1, actions.unsqueeze(1)).squeeze()
        
        # Compute ratio
        ratio = torch.exp(new_log_probs - old_log_probs)
        
        # PPO clip objective
        surr1 = ratio * advantages
        surr2 = torch.clamp(ratio, 1 - self.clip, 1 + self.clip) * advantages
        policy_loss = -torch.min(surr1, surr2).mean()
        
        # KL penalty (stay close to reference model)
        with torch.no_grad():
            ref_logits = self.ref(states)
            kl = kl_divergence(logits, ref_logits)
        
        total_loss = policy_loss + 0.01 * kl
        
        self.optimizer.zero_grad()
        total_loss.backward()
        self.optimizer.step()
        
        return total_loss.item()

Complete RLHF Training Loop

def train_rlhf(policy, ref_model, reward_model, dataloader, epochs=3):
    """Full RLHF training loop."""
    trainer = PPOTrainer(policy, ref_model, reward_model)
    
    for epoch in range(epochs):
        for batch in dataloader:
            # Generate responses from current policy
            responses = generate_responses(policy, batch["prompts"])
            
            # Score with reward model
            rewards = reward_model(responses)
            
            # PPO update
            loss = trainer.ppo_update(...)
            
        print(f"Epoch {epoch}: Policy optimized")

KL Divergence Constraints

A critical component is preventing the policy from drifting too far from the reference model:

def kl_divergence(logits1, logits2):
    """Compute KL divergence between two policy distributions."""
    probs1 = torch.softmax(logits1, dim=-1)
    probs2 = torch.softmax(logits2, dim=-1)
    return (probs1 * (torch.log(probs1) - torch.log(probs2))).sum(-1)

The KL penalty weight balances:

  • Too low: policy may exploit reward model with unrealistic outputs
  • Too high: policy update is too conservative, learning slows

Challenges and Considerations

Reward hacking: Models find ways to game the reward model rather than produce genuinely good outputs.

Human preference saturation: As models improve, human raters may struggle to distinguish better outputs.

Sample efficiency: RLHF requires significant human annotation, typically 10K-100K comparisons per model.

RLHF represents a paradigm shift in how we train AI systems — moving from imitating data to optimizing for human judgment.