Instruction-Following as Alignment Signal
Every later critique of RLHF argues against this pipeline. Before you study how optimization pressure distorts a proxy, you have to see the proxy. InstructGPT (Ouyang et al., 2022) defined the reference architecture: supervised fine-tuning on instruction-response pairs, a reward model trained on pairwise preference rankings, and PPO against the reward model with a KL penalty to the SFT policy. A 1.3B InstructGPT was preferred over a 175B GPT-3. That single result is the reason every frontier lab in 2026 still ships an RLHF-shaped post-training pipeline.
Prerequisites: Phase 10 · 06 (SFT), Phase 10 · 07 (RLHF), Phase 10 · 08 (DPO)
Video coming soon
An AI-narrated walkthrough for this module is on the way. Everything you need is in the lesson below.
#Learning Objectives
- Name the three stages of the InstructGPT pipeline and the loss used in each.
- Explain why a 1.3B instruction-tuned model beat the raw 175B GPT-3 on human preference evaluation.
- State what the KL penalty in stage 3 is protecting against and why removing it collapses to mode-seeking behaviour.
- Describe the alignment tax and the PPO-ptx mitigation Ouyang et al. used against it.
#The Problem
Pre-trained language models complete text. They do not answer questions. Ask GPT-3 "write a Python function that reverses a list" and you often get back another prompt, because most of the training distribution is web text that continues with more web text. The model is doing its job — the job is wrong.
The proxy every serious lab used to fix this is human preference. Two completions go to a rater; the rater picks the better one; a reward model learns the rater. Then an RL loop shifts the policy toward outputs the reward model scores high. That is the full InstructGPT thesis in three sentences. The rest of the paper is engineering.
#The Concept
#Stage 1: supervised fine-tuning (SFT)
Collect prompt-response pairs where the response is what a well-intentioned human would write. Ouyang et al. used 13k prompts from labelers and the OpenAI API. Fine-tune the base model on this data with standard cross-entropy loss.
What SFT gives you: the model now answers questions instead of continuing them. What it does not give you: any signal about which answer the rater prefers when multiple are plausible.
#Stage 2: reward model (RM)
For each prompt, sample K completions from the SFT model. A labeler ranks them. Train a reward model that scores any prompt-response pair so that, for pairs where y_w was preferred over y_l:
L_RM = -log sigmoid(r(x, y_w) - r(x, y_l))This is the Bradley-Terry pairwise preference loss. The RM is usually initialized from the SFT model with the LM head replaced by a scalar head.
Reward models are small: 6B was enough for the 175B InstructGPT. They are also fragile — section 5 of the paper is mostly about reward-hacking behaviours that showed up at small scale.
#Stage 3: PPO with a KL penalty
Define the objective:
J(pi) = E_{x~D, y~pi(.|x)} [ r(x, y) ] - beta * KL(pi(.|x) || pi_SFT(.|x))Maximize with PPO. The KL term keeps pi from drifting far from the SFT policy. Without it, the optimizer finds adversarial examples — strings that score high under the RM because the RM never saw them, not because humans actually prefer them.
The KL coefficient beta is the single most important RLHF hyperparameter. Too low: reward hacking. Too high: no improvement over SFT.
#The alignment tax
After RLHF, the model is preferred by humans but regresses on standard benchmarks (SQuAD, HellaSwag, DROP). Ouyang et al. call this the alignment tax and fix it with PPO-ptx: mix pre-training gradients into the RL objective so the model does not forget how to do downstream tasks it was never rewarded for.
J_ptx(pi) = J(pi) + gamma * E_{x~D_pretrain} [ log pi(x) ]PPO-ptx became standard. Anthropic, DeepMind, and Meta all use some variant.
#The result
A 1.3B InstructGPT (SFT + RM + PPO-ptx) is preferred by labelers over the 175B base GPT-3 about 70% of the time. The gap widens on hidden-test prompts from production traffic. Two things to read off this number:
- Alignment is a different axis from capability. The 175B model had more capability; the 1.3B model had more alignment; labelers preferred the aligned one.
- The capability floor is set by the base model. You cannot RLHF a base model into knowing facts it never saw.
#Why this is the reference point for Phase 18
Every critique in later lessons — reward hacking (Lesson 2), DPO (Lesson 3), sycophancy (Lesson 4), CAI (Lesson 5), sleeper agents (Lesson 7), alignment faking (Lesson 9) — argues against some part of this pipeline. Reward hacking attacks stage 2. DPO collapses stages 2 and 3. CAI replaces the human labeler. Sycophancy shows the labeler is a biased signal. Alignment faking shows the policy can route around stage 3 entirely. You cannot follow any of these critiques without the pipeline in your head first.
#Use It
code/main.py simulates the three stages on toy preference data. The base "policy" is a biased coin over actions {A, B, C}. Stage 1 SFT mimics labeler actions on 200 prompts. Stage 2 fits a Bradley-Terry reward model from 500 pairwise rankings. Stage 3 runs a simplified PPO update with a KL penalty to the SFT policy. You can watch the reward climb, the KL divergence grow, and the policy drift — and you can turn off the KL term to see reward hacking appear inside 50 update steps.
What to look at:
- Reward trajectory with
beta = 0.1vsbeta = 0.0. - KL(pi || pi_SFT) over training steps.
- Final action distribution compared to labeler preference.
#Ship It
This lesson produces outputs/skill-instructgpt-explainer.md. Given an RLHF pipeline description or a paper abstract, it identifies which of the three stages is being modified, what loss is being used at each stage, and whether a KL penalty or equivalent regularizer is present.
#Exercises
Run
code/main.py. Setbeta = 0.0and report the action distribution after 200 PPO steps. Explain the mode-seeking behaviour in one paragraph.Modify the reward model to have a +0.5 bias for action B (a simulated reward bug). Run PPO with
beta = 0.1. Does the KL penalty prevent the policy from exploiting the bias? At whatbetadoes exploitation become visible?Read Ouyang et al. (arXiv:2203.02155) Figure 1. Reproduce the labeler-preference curve by running PPO for 1, 5, 20, 100 steps and measuring preference against the SFT model.
The paper's Section 4.3 reports a 1.3B InstructGPT beats 175B GPT-3 about 70% of the time. Why would the ratio be higher on hidden production prompts than on the labeler's own prompts?
Replace the PPO loss with DPO (Phase 10 · 08) on the same preference data. Compare final policy drift (KL to SFT) and final reward. Which method drifts further at matched reward?
#Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| SFT | "instruction tuning" | Stage 1: cross-entropy fine-tune on prompt-response pairs |
| Reward model | "the RM" | Scalar regressor over (prompt, response) trained with Bradley-Terry on pairwise labels |
| Bradley-Terry | "pairwise preference loss" | -log sigmoid(r_w - r_l); reduces pairwise ranking to binary classification |
| KL penalty | "the regularizer" | beta * KL(pi || pi_SFT) — keeps the RL policy near the SFT anchor |
| PPO-ptx | "PPO with pretraining mix" | Adds a fraction of pre-training log-likelihood to the PPO objective to offset the alignment tax |
| Alignment tax | "the RLHF regression" | Post-RLHF drop on standard benchmarks that RLHF did not target |
| Labeler preference | "the ground truth" | Sample of human rankings; the RM is a statistical proxy for this, not for "human values" |
#Further Reading
- Ouyang et al. — Training language models to follow instructions with human feedback (arXiv:2203.02155) — the InstructGPT paper, foundation for every RLHF pipeline that followed
- Stiennon et al. — Learning to summarize from human feedback (arXiv:2009.01325) — the RLHF-for-summarization predecessor
- Christiano et al. — Deep reinforcement learning from human preferences (arXiv:1706.03741) — the original preference-based RL formulation
- Bai et al. — Training a Helpful and Harmless Assistant with RLHF (arXiv:2204.05862) — Anthropic's HH extension of the InstructGPT pipeline
Run this lesson in the Code Lab
A real JupyterLab, with this lesson's code files (and a ready-to-run notebook). The Quick Lab opens instantly in your browser. The Cloud Lab adds a terminal, saved files and the full toolchain.
"""Toy three-stage RLHF pipeline — stdlib Python.
Simulates InstructGPT's SFT + RM + PPO loop on a bandit with three actions.
Watch reward climb, KL divergence grow, and the policy drift. Turn off the
KL penalty to see reward hacking appear. Pedagogical toy — no torch.
Usage: python3 code/main.py
"""
from __future__ import annotations
import math
import random
from dataclasses import dataclass, field
random.seed(0)
ACTIONS = ["A", "B", "C"]
def softmax(logits: list[float]) -> list[float]:
m = max(logits)
exps = [math.exp(x - m) for x in logits]
z = sum(exps)
return [e / z for e in exps]
def kl(p: list[float], q: list[float]) -> float:
return sum(pi * math.log(pi / qi) for pi, qi in zip(p, q) if pi > 0 and qi > 0)
@dataclass
class Policy:
"""Softmax policy over 3 actions. Logits are the trainable parameters."""
logits: list[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])
def probs(self) -> list[float]:
return softmax(self.logits)
def sample(self) -> int:
r = random.random()
cum = 0.0
for i, p in enumerate(self.probs()):
cum += p
if r < cum:
return i
return len(self.logits) - 1
def logprob(self, a: int) -> float:
return math.log(self.probs()[a] + 1e-12)
def copy(self) -> "Policy":
return Policy(logits=list(self.logits))
def labeler_true_utility() -> list[float]:
"""The 'human' rater prefers B, is neutral on A, slightly against C."""
return [0.0, 1.0, -0.3]
def stage1_sft(n_demos: int = 200) -> Policy:
"""Imitation learning from labeler demonstrations.
Labeler samples actions with probabilities softmax(utility). SFT maximum-
likelihood estimates this distribution with a single-step gradient move.
"""
utility = labeler_true_utility()
target = softmax(utility)
demos = []
for _ in range(n_demos):
r = random.random()
cum = 0.0
for i, p in enumerate(target):
cum += p
if r < cum:
demos.append(i)
break
# closed-form MLE for categorical: log count frequencies
counts = [0.0, 0.0, 0.0]
for a in demos:
counts[a] += 1
total = sum(counts)
logits = [math.log(c / total + 1e-6) for c in counts]
# center for numerical stability
m = sum(logits) / 3
logits = [x - m for x in logits]
return Policy(logits=logits)
def stage2_reward_model(n_pairs: int = 500, bias: list[float] | None = None) -> list[float]:
"""Bradley-Terry fit of a scalar reward over actions.
Labeler prefers action with higher true utility. We fit one scalar per
action by SGD on pairwise cross-entropy. Optional `bias` injects a
reward-model bug (used in Exercise 2).
"""
utility = labeler_true_utility()
r = [0.0, 0.0, 0.0]
lr = 0.05
for _ in range(n_pairs):
i, j = random.sample(range(3), 2)
p_prefer_i = 1 / (1 + math.exp(-(utility[i] - utility[j])))
winner = i if random.random() < p_prefer_i else j
loser = j if winner == i else i
# BT gradient: dL/dr_w = -(1 - sigmoid(r_w - r_l))
diff = r[winner] - r[loser]
s = 1 / (1 + math.exp(-diff))
r[winner] += lr * (1 - s)
r[loser] -= lr * (1 - s)
if bias:
r = [ri + bi for ri, bi in zip(r, bias)]
# center reward (RL is invariant to constant shifts)
m = sum(r) / 3
return [x - m for x in r]
def stage3_ppo(sft: Policy, reward: list[float], beta: float,
steps: int = 300, batch: int = 32,
lr: float = 0.1) -> tuple[Policy, list[float], list[float]]:
"""Toy REINFORCE-with-KL (a stripped-down PPO).
For each step: sample a batch from current policy, take a policy-gradient
step on `r(a) - beta * log(pi / pi_sft)`. Tracks mean reward and KL.
"""
pi = sft.copy()
reward_traj: list[float] = []
kl_traj: list[float] = []
sft_probs = sft.probs()
for _ in range(steps):
advantages = [0.0, 0.0, 0.0]
counts = [0, 0, 0]
total_r = 0.0
for _ in range(batch):
a = pi.sample()
r_a = reward[a]
# KL-shaped per-sample reward
penalty = beta * (math.log(pi.probs()[a] + 1e-12)
- math.log(sft_probs[a] + 1e-12))
shaped = r_a - penalty
advantages[a] += shaped
counts[a] += 1
total_r += r_a
for a in range(3):
if counts[a] > 0:
advantages[a] /= counts[a]
# softmax policy gradient: grad logit_a = (1_{a} - pi_a) * advantage
probs = pi.probs()
grad = [0.0, 0.0, 0.0]
for a in range(3):
for b in range(3):
indicator = 1.0 if a == b else 0.0
grad[b] += advantages[a] * probs[a] * (indicator - probs[b])
pi.logits = [l + lr * g for l, g in zip(pi.logits, grad)]
reward_traj.append(total_r / batch)
kl_traj.append(kl(pi.probs(), sft_probs))
return pi, reward_traj, kl_traj
def report(name: str, sft: Policy, rlhf: Policy, reward: list[float],
r_traj: list[float], kl_traj: list[float]) -> None:
print(f"\n{name}")
print("-" * 60)
print(f" SFT probs : {[f'{p:.3f}' for p in sft.probs()]}")
print(f" RLHF probs : {[f'{p:.3f}' for p in rlhf.probs()]}")
print(f" Reward model : {[f'{r:+.3f}' for r in reward]}")
print(f" Final reward : {r_traj[-1]:+.3f}")
print(f" Final KL : {kl_traj[-1]:.3f} nats")
print(f" Max reward : {max(r_traj):+.3f} at step {r_traj.index(max(r_traj))}")
def main() -> None:
print("=" * 60)
print("INSTRUCTGPT TOY PIPELINE (Phase 18, Lesson 1)")
print("=" * 60)
sft = stage1_sft()
print("\nStage 1 SFT complete.")
print(f" SFT policy: {[f'{p:.3f}' for p in sft.probs()]}")
rm = stage2_reward_model()
print("\nStage 2 RM complete.")
print(f" Reward per action: {[f'{r:+.3f}' for r in rm]}")
# Standard RLHF: small-beta KL keeps us near SFT.
rlhf, r_traj, kl_traj = stage3_ppo(sft, rm, beta=0.1)
report("Run 1: beta = 0.10 (standard InstructGPT)", sft, rlhf, rm, r_traj, kl_traj)
# Reward hacking: kill the KL.
rlhf2, r2, kl2 = stage3_ppo(sft, rm, beta=0.0)
report("Run 2: beta = 0.00 (no KL — reward hacking shows up)",
sft, rlhf2, rm, r2, kl2)
# RM bug: +0.5 bias on action A. With KL on, partial exploitation.
rm_buggy = stage2_reward_model(bias=[0.5, 0.0, 0.0])
rlhf3, r3, kl3 = stage3_ppo(sft, rm_buggy, beta=0.1)
report("Run 3: buggy RM (+0.5 on action A), beta = 0.10",
sft, rlhf3, rm_buggy, r3, kl3)
print("\n" + "=" * 60)
print("TAKEAWAY: KL penalty trades reward for faithfulness. beta is the")
print("single most important RLHF hyperparameter. beta = 0 is not PPO;")
print("it is adversarial optimization against an imperfect proxy.")
print("=" * 60)
if __name__ == "__main__":
main()
6 questions on this lesson
Answer each one and see straight away whether you were right and why. Your first answer to a question counts. You are not signed in, so this is practice and nothing is saved.
Reusable outputs from this lesson. Copy them into your own work.
---
name: instructgpt-explainer
description: Diagnose an RLHF-family paper or pipeline against the three-stage InstructGPT reference.
version: 1.0.0
phase: 18
lesson: 1
tags: [rlhf, instructgpt, sft, reward-model, ppo, alignment]
---
Given a paper abstract, blog post, or pipeline description that claims to "align" a language model, identify which stages of the InstructGPT reference (SFT + RM + PPO-ptx with KL penalty) the method modifies, and what is at risk when each stage changes.
Produce:
1. Stage-by-stage mapping. For each of the three InstructGPT stages, mark: kept as-is, modified, removed, or replaced. For every non-"kept" cell, name the replacement (e.g. "Stage 2: replaced by closed-form implicit reward — DPO").
2. Regularizer check. Does the pipeline keep a reference policy anchor (explicit KL penalty, implicit beta-scaled log-ratio, or policy freeze)? If not, flag the risk of reward hacking under any imperfect proxy.
3. Preference-source audit. Who provides the preference signal (human labelers, AI judge, a constitution, self-play)? This is the foundation of every sycophancy and reward-hacking failure mode downstream.
4. Alignment-tax check. Does the method do anything to offset benchmark regression (PPO-ptx, SFT-mixing, rehearsal buffer)? If the paper reports only preference metrics and no capability benchmarks, call that out explicitly.
Hard rejects:
- Any claim that RLHF teaches new facts. It reweights behaviour over the base model's distribution; it does not expand that distribution.
- Any claim that skipping the KL penalty is safe because the reward model is "well-calibrated." Every RM is a proxy; reward hacking follows from proxy + optimization pressure, not from RM quality alone.
- Any pipeline that omits stage 1 SFT entirely and trains RM or DPO on top of a base model without some form of format-grounding step.
Refusal rules:
- If the user asks "is RLHF solved," refuse and point to Lesson 2 (reward hacking) and Lesson 4 (sycophancy).
- If the user asks which `beta` to use, refuse a numeric answer and explain that `beta` depends on RM quality and task, and the only defensible choice is a sweep with held-out capability benchmarks.
Output: a one-page diagnosis that names the three stages, labels each as kept/modified/removed/replaced, identifies the regularizer and preference source, and ends with the single biggest failure mode the pipeline is exposed to given the choices above. Cite InstructGPT (arXiv:2203.02155) once as the reference point.
The text and code of this lesson are based on AI Engineering from Scratch by Rohit Ghumare, MIT licensed.