Use when implementing RLHF for training language models.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add LoopyLuci/Skills --skill rlhf-implementation-guide --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Rlhf Implementation Guide?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/loopyluci-rlhf-implementation-guide)More formats (shields.io, HTML) on the badges page.
---
name: rlhf-implementation-guide
description: "Use when implementing RLHF for training language models."
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
hermes:
tags: [RLHF, reinforcement-learning, LLM, training, human-feedback]
related_skills: [reinforcement-learning, llm-fine-tuning-lora, deep-reinforcement-learning, agent-safety-alignment]
---
# RLHF (Reinforcement Learning from Human Feedback)
Implementing RLHF to align language models with human preferences — from reward model training to PPO-based fine-tuning, with practical patterns for stability, efficiency, and safety.
## When to Use
- Aligning LLMs with human preferences (helpfulness, harmlessness, honesty)
- Fine-tuning models to follow instructions more reliably
- Reducing harmful outputs without sacrificing capability
- Building models that reflect specific value systems
- Producing the final alignment stage after supervised fine-tuning
## RLHF Pipeline
```
SFT Model → Collect Preferences → Train Reward Model → PPO Fine-tune → Aligned Model
```
### Stage 1: Supervised Fine-Tuning (SFT)
```python
# Before RLHF, the model should be instruction-tuned via SFT
# This provides a good initialization for the RLHF process
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("base-model")
tokenizer = AutoTokenizer.from_pretrained("base-model")
# SFT on demonstration data
# (Standard language modeling on human-written responses)
```
### Stage 2: Collect Human Preferences
```python
"""
Preference data format:
{
"prompt": "Explain quantum computing",
"chosen": "Quantum computing uses qubits...", # Preferred response
"rejected": "It's like magic computers..." # Dispreferred response
}
Typically 10K-100K preference pairs are needed.
Data can be collected via:
1. Human raters comparing model outputs
2. AI feedback (RLAIF) using a strong model as judge
3. User interaction logs (upvote/downvote)
"""
```
### Stage 3: Train Reward Model
```python
import torch
import torch.nn as nn
from transformers import AutoModel, AutoTokenizer
class RewardModel(nn.Module):
"""Reward model: scores how good a response is.
Usually initialized from the SFT model with a value head."""
def __init__(self, base_model_name="sft-model", dropout=0.1):
super().__init__()
self.base_model = AutoModel.from_pretrained(base_model_name)
hidden_size = self.base_model.config.hidden_size
# Value head: maps last hidden state to a scalar reward
self.value_head = nn.Sequential(
nn.Dropout(dropout),
nn.Linear(hidden_size, 1)
)
def forward(self, input_ids, attention_mask=None):
outputs = self.base_model(input_ids, attention_mask=attention_mask)
# Use the last token's hidden state as the pooled representation
last_hidden = outputs.last_hidden_state
# For causal LMs, take the last non-padding token
if attention_mask is not None:
last_token_indices = attention_mask.sum(dim=1) - 1
batch_indices = torch.arange(last_hidden.shape[0], device=last_hidden.device)
pooled = last_hidden[batch_indices, last_token_indices]
else:
pooled = last_hidden[:, -1, :]
reward = self.value_head(pooled).squeeze(-1)
return reward
def train_reward_model(model, dataloader, tokenizer, epochs=3, lr=1e-5):
"""Train reward model on preference pairs using Bradley-Terry loss.
Loss: -log(σ(r_chosen - r_rejected))
"""
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
for epoch in range(epochs):
total_loss = 0
for batch in dataloader:
# Tokenize chosen and rejected responses
chosen_inputs = tokenizer(batch['chosen'], return_tensors='pt', padding=True)
rejected_inputs = tokenizer(batch['rejected'], return_tensors='pt', padding=True)
# Get rewards
r_chosen = model(**chosen_inputs)
r_rejected = model(**rejected_inputs)
# Bradley-Terry loss
loss = -torch.log(torch.sigmoid(r_chosen - r_rejected) + 1e-8).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
# Accuracy: how often does the RM prefer the chosen response?
acc = (r_chosen > r_rejected).float().mean()
print(f"Epoch {epoch}: loss={total_loss/len(dataloader):.4f}, acc={acc:.4f}")
return model
```
### Stage 4: PPO Fine-Tuning
```python
class PPOTrainer:
"""PPO for RLHF. Key components:
- Policy (the model being trained)
- Reference model (frozen, for KL penalty)
- Reward model (frozen, for scoring)
- Value model (usually same as policy with a value head)
"""
def __init__(self, policy_model, ref_model, reward_model,
tokenizer, lr=1e-6, kl_coef=0.1, clip_epsilon=0.2):
self.policy = policy_model
self.ref_model = ref_model
self.ref_model.eval()
for p in self.ref_model.parameters():
p.requires_grad = False
self.reward_model = reward_model
self.reward_model.eval()
for p in self.reward_model.parameters():
p.requires_grad = False
self.tokenizer = tokenizer
self.optimizer = torch.optim.AdamW(self.policy.parameters(), lr=lr)
self.kl_coef = kl_coef
self.clip_epsilon = clip_epsilon
def compute_kl_penalty(self, policy_logprobs, ref_logprobs):
"""KL divergence between policy and reference model.
Acts as a trust region to prevent policy from diverging too far."""
ratio = torch.exp(policy_logprobs - ref_logprobs)
kl = (ratio - 1 - (policy_logprobs - ref_logprobs)).mean()
return kl
def compute_advantages(self, rewards, values, gamma=1.0, lam=0.95):
"""GAE (Generalized Advantage Estimation) for the reward signals."""
advantages = []
gae = 0
for t in reversed(range(len(rewards))):
if t == len(rewards) - 1:
next_value = 0
else:
next_value = values[t + 1]
delta = rewards[t] + gamma * next_value - values[t]
gae = delta + gamma * lam * gae
advantages.insert(0, gae)
returns = [adv + val for adv, val in zip(advantages, values)]
return advantages, returns
def generate_experience(self, prompts, max_length=512):
"""Generate responses using current policy for PPO training."""
experiences = []
for prompt in prompts:
# Tokenize
inputs = self.tokenizer(prompt, return_tensors='pt')
input_ids = inputs['input_ids']
attention_mask = inputs['attention_mask']
# Generate response from policy
with torch.no_grad():
# Greedy or sample-based generation
response_ids = self.policy.generate(
input_ids,
max_length=max_length,
do_sample=True,
temperature=0.7,
pad_token_id=self.tokenizer.eos_token_id
)
# Get logprobs for the generated tokens
outputs = self.policy(response_ids, attention_mask=attention_mask)
policy_logprobs = torch.log_softmax(outputs.logits, dim=-1)
# Get reference logprobs
ref_outputs = self.ref_model(response_ids, attention_mask=attention_mask)
ref_logprobs = torch.log_softmax(ref_outputs.logits, dim=-1)
# Get reward
reward = self.reward_model(response_ids, attention_mask=attention_mask)
experiences.append({
'input_ids': response_ids,
'attention_mask': attention_mask,
'policy_logprobs': policy_logprobs,
'ref_logprobs': ref_logprobs,
'reward': reward.item()
})
return experiences
def train_step(self, experiences):
"""Single PPO update step."""
for exp in experiences:
input_ids = exp['input_ids']
attention_mask = exp['attention_mask']
# Current policy logprobs
outputs = self.policy(input_ids, attention_mask=attention_mask)
logprobs = torch.log_softmax(outputs.logits, dim=-1)
# Ratio for PPO clipping
ratio = torch.exp(logprobs - exp['policy_logprobs'])
# KL penalty
kl = self.compute_kl_penalty(logprobs, exp['ref_logprobs'])
# PPO clipped objective
reward = exp['reward'] - self.kl_coef * kl
advantages = reward # Simplified (no GAE for single-token rewards)
pg_loss = -torch.min(
ratio * advantages,
torch.clamp(ratio, 1 - self.clip_epsilon, 1 + self.clip_epsilon) * advantages
).mean()
self.optimizer.zero_grad()
pg_loss.backward()
torch.nn.utils.clip_grad_norm_(self.policy.parameters(), 1.0)
self.optimizer.step()
return pg_loss.item()
```
## Reward Shaping
```python
def composite_reward(reward_model_score, safety_score=None, length_penalty=0.0):
"""Combine multiple reward signals."""
reward = reward_model_score
if safety_score is not None:
# Penalize unsafe responses
reward -= safety_score_coef * max(0, safety_threshold - safety_score)
if length_penalty != 0:
# Optional length penalty (shorter responses often preferred)
pass
return reward
def reward_normalize(rewards):
"""Normalize rewards to have zero mean and unit variance.
Crucial for PPO stability."""
rewards = torch.tensor(rewards)
return (rewards - rewards.mean()) / (rewards.std() + 1e-8)
```
## Efficiency Optimizations
```python
# LoRA for RLHF: only train LoRA adapters instead of full model
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05
)
policy_model = get_peft_model(policy_model, lora_config)
# Reward model can also be LoRA-tuned
# This dramatically reduces memory requirements
```
## Common Pitfalls
1. **Reward hacking** — model exploits reward signal in unintended ways; use KL penalty and diverse reward signals
2. **KL collapse** — too strong KL penalty prevents learning; too weak allows mode collapse; tune carefully
3. **Catastrophic forgetting** — model loses language capabilities; mix in SFT loss during PPO (10-20% weight)
4. **Reward model over-optimization** — RM scores improve but actual quality degrades; use held-out RM and human eval
5. **Mode collapse** — policy produces low-diversity outputs; increase entropy bonus or use diverse prompts
6. **Memory explosion** — policy + reference + reward model + value model = 4x memory; use LoRA or offloading
## Verification Checklist
- [ ] Reward model achieves >70% accuracy on held-out preference pairs
- [ ] PPO training increases average reward without increasing KL beyond threshold
- [ ] Human evaluation shows improvement over SFT baseline
- [ ] No reward hacking detected (qualitative review of high-reward outputs)
- [ ] Model retains language capabilities (standard benchmark score drop < 5%)
- [ ] Output diversity maintained (distinct-N, perplexity metrics)
- [ ] Safety benchmarks don't regress
## See Also
- reinforcement-learning — foundational RL concepts
- llm-fine-tuning-lora — efficient fine-tuning for RLHF
- deep-reinforcement-learning — PPO implementation details
- agent-safety-alignment — safety evaluation for aligned models
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!