Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
Suppose a release candidate raises its internal judge score by . Reviewers prefer its answers less often, and responses are 48 tokens longer because the model learned a "Policy-safe answer:" preamble. Would you promote it? No. One proxy moved; product behavior moved the wrong way.
The last chapter turned one access-policy pair into a scalar. For the prompt "temporary admin access for tonight's migration," the ticket-and-cite answer scored and the grant-tonight answer scored . The Bradley-Terry preference model maps that margin to roughly a chance that the labeler prefers the safer answer. This chapter asks what to do with that judge: train a policy against it, or skip the extra model and optimize the policy directly from the pairs?
An instruction-tuned assistant can still answer that prompt with fluent, confident, and wrong guidance. InstructGPT puts the gap plainly: a model trained to predict plausible text can still be unhelpful, untruthful, or unsafe even when the prose looks polished.[1]
That's the alignment problem. Pretraining optimizes next-token prediction. Preference methods such as RLHF (Reinforcement Learning from Human Feedback) and DPO (Direct Preference Optimization) push the model toward answers a labeling policy prefers. They don't, by themselves, prove truthfulness or safety outside that policy's coverage. Whether learned or held out, a judge remains a proxy.
The gap SFT can't close
After instruction tuning, the model can follow the shape of a request. It can still grant a privileged role, refuse a reasonable one, or pad a correct answer until it's unusable. Next-token prediction doesn't enforce a product's rules. Predicting what comes next isn't the same as satisfying the behavior being judged.
Keep the same prompt the reward-modeling chapter used: "Policy: cite the retrieved access policy and escalate privileged-role changes. User: My service account needs temporary admin access for tonight's migration. Can you approve it?" Two SFT completions are both grammatical:
- Response A: "The retrieved access policy requires escalation for temporary admin access. I can open a reviewer ticket and cite policy
P-7." - Response B: "Temporary admin access is common for migrations, so you can proceed and clean it up tomorrow."
A follows the supplied policy. B violates it. Before preference labels enter, ask what SFT can infer from this row: both strings are grammatical, but only A makes escalation explicit. Unless demonstrations encode that distinction, next-token loss has no term that marks B as a product failure. Preference training supplies that comparative signal when the distinction appears in labeled data.[1]

RLHF: a judge, then an actor
RLHF is the large-scale preference pipeline InstructGPT popularized, building on earlier human-feedback fine-tuning for summarization: start from an SFT model, train a reward model from comparisons, then optimize the policy with reinforcement learning.[2][1]
The pipeline is a handoff. SFT gives instruction-following. The reward model turns comparisons into a scalar. PPO updates the policy while a KL term charges drift away from the SFT reference.
Three-phase pipeline
Treat each phase as answering one question: can the model imitate useful answers, can a scorer reproduce the preference rule, and can the policy improve under that scorer without leaving tested behavior? The first two use stored examples. The third generates and scores policy outputs.

Phase 3 is where the extra machinery shows up. InstructGPT's RL stage is a bandit: a prompt comes in, the policy writes a response, the reward model returns a scalar, and a per-token KL penalty against the SFT model is added to limit overoptimization.[1] That fresh response is the key difference from fitting only the stored demonstrations.
Bradley-Terry, one number later
Here is how the judge turns a pair into a training signal. Predict the direction first: because A's score is higher, its preference probability should be above ; reversing the scores should make the observed A label less likely. The Bradley-Terry model says the probability that a human prefers over is a sigmoid of the reward gap:[1][3]
Plug in last chapter's scores: , . The gap is , so . If the human picked A, the negative log-likelihood of that observation is . Flip the ranking and the same math becomes . The loss pushes the reward model toward the higher-probability assignment.
Why does a larger reward gap make the preference probability closer to 1?
Answer
The sigmoid receives a larger positive input, so it moves toward 1. In Bradley-Terry terms, a larger score difference means the model is more confident the higher-scored response wins.
1import math
2
3def sigmoid(value: float) -> float:
4 return 1 / (1 + math.exp(-value))
5
6reward_a = 1.8
7reward_b = 0.7
8preference_probability = sigmoid(reward_a - reward_b)
9negative_log_likelihood = -math.log(preference_probability)
10
11print(f"P(A preferred over B) = {preference_probability:.1%}")
12print(f"NLL = {negative_log_likelihood:.3f}")1P(A preferred over B) = 75.0%
2NLL = 0.287The training loss is the same expectation you saw for the reward model:
where is the winning response and is the losing response for the same prompt .
PPO and the KL budget
Once you have a reward model, ask what prevents the policy from chasing its score by drifting. Treat the language model as a policy . Proximal Policy Optimization (PPO) makes clipped, conservative updates.[4] The KL-regularized objective is reward minus a penalty for leaving the reference:
The Kullback-Leibler term is a drift budget, not a proof that the reward is right. InstructGPT applies that penalty per token specifically to mitigate reward-model overoptimization.[1]
That budget matters on this prompt. Suppose the policy starts opening with "Policy-safe answer: access guidance follows." If the reward model scores that preamble higher because it correlated with polite answers in the comparison set, the policy will amplify it. KL charges the departure from the SFT reference. Held-out humans still decide whether behavior actually improved.
To see why this is a system rather than one loss, track four roles in one PPO-style step. Real systems may shard or share weights, but the computation still has to cover all four:
- Actor / policy (): the model being trained
- Reference (): frozen SFT model for the KL penalty
- Reward model (): frozen scorer
- Critic / value (): predicts expected return for advantage estimation

The snippet below isolates shaped reward for two sampled responses. Before reading its output, predict the ordering: the preamble has raw reward but log-ratio , versus for the near-reference sample. With , KL should erase that apparent win. This isn't a PPO trainer. A full implementation also estimates advantages, clips updates, and applies token-level masks.
1samples = [
2 {"name": "near_reference", "reward": 1.20, "policy_logp": -2.1, "ref_logp": -2.2},
3 {"name": "preamble_hack", "reward": 1.35, "policy_logp": -1.2, "ref_logp": -2.8},
4]
5beta = 0.20
6
7for sample in samples:
8 sampled_log_ratio = sample["policy_logp"] - sample["ref_logp"]
9 shaped_reward = sample["reward"] - beta * sampled_log_ratio
10 print(f"{sample['name']}: raw={sample['reward']:.2f} shaped={shaped_reward:.2f}")1near_reference: raw=1.20 shaped=1.18
2preamble_hack: raw=1.35 shaped=1.03The output makes the ordering explicit: the higher raw score isn't automatically the better update once drift is charged. Production PPO stacks token-level KL, a value model, and often Generalized Advantage Estimation (GAE) on top of this shape, and it generates fresh responses each cycle.[1][4]
Why this loop is expensive
The four roles create a coordination cost first. Sharding changes resident memory, not the fact that you're generating and scoring on-policy text before the gradient step.
That loop exposes a wide tuning surface: reward scale, the KL coefficient (beta), policy-ratio clip threshold, value-loss weight, advantage estimation, and rollout quality. Weak drift control invites reward-model exploitation. Overly strong control can freeze useful updates.[1][4]
Both costs would be manageable if the score stayed trustworthy. The reward model saw a finite set of comparisons. Once the improving policy leaves that distribution, its score no longer necessarily means "humans would prefer this." That is overoptimization: numeric reward climbs while held-out human preference flattens or drops. On this assistant, a longer compliance preamble is an easy tell because it can score well without fixing the access decision.
| Issue | What it looks like |
|---|---|
| Four roles | Policy, reference, reward, and value, with memory depending on sharding |
| Tuning surface | Clip ratio, learning rate, value-loss weights, reward scale, sample quality |
| Reward hacking | Policy exploits RM weaknesses on out-of-distribution text |
| Coverage gaps | Behaviors missing from the reward data can regress |
| Cost | Human comparisons are expensive; on-policy generation is slow |
DPO: the same objective without the extra judge
PPO-style RLHF is a multi-role online loop. If a fixed pair already tells you which response wins, why generate another response for every update? DPO targets those pairs with a classification-style loss. It removes engineering, but it doesn't give you online exploration or guarantee the same checkpoint PPO would have found.
Why an offline loss can stand in for RL
Under a KL-regularized reward-maximization objective and a Bradley-Terry preference model, the optimal policy and the reward are two views of the same object. DPO parameterizes an implicit reward with policy-to-reference log-probability ratios, so the pairwise loss never needs a standalone reward model or a PPO loop.[3] Before the algebra, check its anchor: if policy equals reference, both relative log-ratios are zero, so the DPO logit is zero and the pair probability is .
The implicit reward of the optimal policy is:
is that optimal policy, is the reference, is the KL trade-off (and the DPO logit scale), and is a partition function that depends only on the prompt. Because Bradley-Terry uses a difference of rewards, cancels, and you can train a policy directly from pairs.[3]
Why does the reference policy appear in DPO?
Answer
It anchors the aligned model to the base behavior. DPO rewards making preferred responses more likely relative to the reference while discouraging uncontrolled drift.
Work the loss on the P-7 pair
Substitute the implicit reward into the preference loss:
is chosen (ticket and cite P-7) and is rejected (grant admin tonight). Read each term as change relative to the frozen reference: raise the chosen response relative to that baseline, lower the rejected response, and the margin grows. The reference is a baseline, not a safety proof.
Suppose the current sequence log-probabilities are:
- Chosen A: policy , reference
- Rejected B: policy , reference
Then:
- Chosen relative advantage:
- Rejected relative advantage:
- Margin:
- Scaled logit:
- Loss:
The update tries to grow that margin: raise chosen likelihood relative to the reference, lower rejected likelihood, scaled by how far the policy already sits from the reference.
1from math import exp, log
2
3chosen_policy, chosen_reference = -8.2, -8.5
4rejected_policy, rejected_reference = -9.5, -9.1
5beta = 0.1
6margin = (chosen_policy - chosen_reference) - (rejected_policy - rejected_reference)
7logit = beta * margin
8loss = -log(1 / (1 + exp(-logit)))
9
10print(f"relative_margin={margin:.1f}")
11print(f"scaled_logit={logit:.2f}")
12print(f"dpo_loss={loss:.3f}")1relative_margin=0.7
2scaled_logit=0.07
3dpo_loss=0.659Those figures are already summed sequence log-probabilities. In a trainer they come from a causal shift: concatenate prompt and response, take next-token logits on the response span, log-softmax, gather the observed token, and sum. The next snippet does that on a four-token vocab for the same chosen/rejected split. Predict the result: boosting chosen-token logits should raise chosen log-probability and lower the loss while the frozen reference stays fixed. Padding masks, attention masks, and distributed training stay out of this example.
1import math
2
3CHOSEN_IDS = [1, 2]
4REJECTED_IDS = [3, 0]
5BETA = 0.1
6
7chosen_policy = [
8 [0.0, 2.4, 0.1, -0.8],
9 [0.0, 0.2, 2.1, -0.6],
10]
11chosen_ref = [
12 [0.0, 0.8, 0.2, 0.0],
13 [0.0, 0.1, 0.9, 0.0],
14]
15rejected_policy = [
16 [0.2, -0.4, -0.2, 1.1],
17 [1.0, -0.3, -0.4, 0.2],
18]
19rejected_ref = [
20 [0.1, 0.0, 0.0, 0.6],
21 [0.7, 0.0, 0.0, 0.2],
22]
23
24def log_softmax(logits: list[float]) -> list[float]:
25 peak = max(logits)
26 weights = [math.exp(value - peak) for value in logits]
27 total = sum(weights)
28 return [math.log(weight / total) for weight in weights]
29
30def sequence_logprob(steps: list[list[float]], token_ids: list[int]) -> float:
31 return sum(
32 log_softmax(logits)[token_id]
33 for logits, token_id in zip(steps, token_ids, strict=True)
34 )
35
36def dpo_loss(
37 chosen_pi: list[list[float]],
38 rejected_pi: list[list[float]],
39) -> float:
40 margin = (
41 sequence_logprob(chosen_pi, CHOSEN_IDS)
42 - sequence_logprob(chosen_ref, CHOSEN_IDS)
43 - (
44 sequence_logprob(rejected_pi, REJECTED_IDS)
45 - sequence_logprob(rejected_ref, REJECTED_IDS)
46 )
47 )
48 logit = BETA * margin
49 return -math.log(1.0 / (1.0 + math.exp(-logit)))
50
51loss = dpo_loss(chosen_policy, rejected_policy)
52boosted_chosen = [
53 [0.0, 3.4, 0.1, -0.8],
54 [0.0, 0.2, 3.1, -0.6],
55]
56boosted_loss = dpo_loss(boosted_chosen, rejected_policy)
57
58print(f"chosen_logp={sequence_logprob(chosen_policy, CHOSEN_IDS):.3f}")
59print(f"rejected_logp={sequence_logprob(rejected_policy, REJECTED_IDS):.3f}")
60print(f"dpo_loss={loss:.3f}")
61print(f"after_boost_loss={boosted_loss:.3f}")
62assert math.isfinite(loss) and boosted_loss < loss1chosen_logp=-0.501
2rejected_logp=-1.320
3dpo_loss=0.665
4after_boost_loss=0.650At initialization, a policy copied from its reference has zero reference-relative log-ratios, so the DPO logit is zero and the loss is near . The model's token logits themselves need not be zero. After training starts, watch margins together with held-out preference quality and output regressions. A near-zero DPO logit alone doesn't diagnose weak pairs or a bad beta.
Production trainers also mask padding and track response length, because a whole-sequence log-prob sum can create length bias when chosen and rejected answers differ systematically in length.
Likelihood displacement: a bigger margin isn't a more likely chosen answer
Pair arithmetic above hides a second question: did the policy make the chosen answer more likely, or only make the rejected answer less likely? A rising DPO margin isn't the same as "the preferred answer is getting more likely." DPO tracks the gap between chosen and rejected relative log-ratios. Lowering and together can still look like a win when the rejected side falls faster.
Keep the P-7 pair's frozen reference at (chosen) and (rejected). If chosen log-probability slides from to while rejected slides from to , the relative margin grows from to even though both labeled completions are being avoided. Plotting makes that gap the thing you see: chosen starts at and finishes at , so the preferred answer is now less likely than the SFT reference.

1ref_w, ref_l = -8.5, -9.1
2steps = [
3 ("step0", -8.2, -9.5),
4 ("step3", -9.1, -11.2),
5]
6
7for name, pi_w, pi_l in steps:
8 margin = (pi_w - ref_w) - (pi_l - ref_l)
9 print(f"{name}: chosen={pi_w:.1f} rejected={pi_l:.1f} margin={margin:.1f}")1step0: chosen=-8.2 rejected=-9.5 margin=0.7
2step3: chosen=-9.1 rejected=-11.2 margin=1.5Monitor at least four series:
| Signal | What it catches |
|---|---|
| Whether preferred answers stay likely | |
| Whether rejected answers are suppressed | |
| Relative margin (DPO logit) | Whether pairwise ranking is improving |
| KL or log-ratio to | Whether the policy is leaving the reference |
If margins improve while chosen log-prob collapses, stop. Inspect data, , length bias, and offline coverage before more steps. Length-matched pairs, length-normalized log-probs, or length-aware objectives such as SimPO are the usual next tests.
Vanilla DPO is still just that pairwise loss plus a frozen reference. No reward model, no value model, no rollout.

Comparing RLHF and DPO
The useful contrast is what each method has to keep alive, not a winner-take-all ranking.[1][3] Read the table as a deployment question: which signal must stay live, and which behavior can your stored data never show you?

| Feature | RLHF (PPO) | DPO |
|---|---|---|
| Reward model | Train a separate reward model | No separate RM; reward is implicit |
| Training loop | On-policy RL with rollouts | Offline pairwise loss on fixed comparisons |
| Logical roles | Policy + ref + reward + critic | Policy + reference |
| Most expensive step | Sampling and scoring fresh responses | Forward/backward on stored pairs |
| Stability | Sensitive to reward scale, KL, and PPO settings | Fewer loops, still sensitive to data and beta |
| Online exploration | Yes | Not in vanilla DPO |
| Data dependence | Can collect fresh comparisons during training | Limited by current pair coverage unless you refresh |
| Monitoring | Reward drift, KL, value loss, rollout quality | Preference loss, chosen log-prob, margin, length |
DPO is a strong candidate baseline for offline pairwise preferences. PPO isn't obsolete. If you need online exploration, fresh model-generated negatives, or a reward that moves as the policy moves, an RL-style loop can reach cases a frozen DPO dataset can't.
The data both methods inherit
RLHF and DPO consume the same kind of row, then spend it differently. RLHF fits a reward model on the comparisons, then rolls out. DPO trains the policy on the pairs themselves.[1][3] The row is shared; the feedback path is not.
The hygiene from the reward-modeling chapter still applies: same rendered prompt, a clear non-tie label, generator provenance, and prompt-grouped splits so related comparisons don't leak into evaluation. A pair from another generator isn't automatically invalid, but it can miss the failure modes your current policy actually produces. When shift matters, include current-policy samples.
You don't need a huge dump of easy instructions. You need coverage of the distinctions you care about: refusals, tool use, reasoning, style, and the P-7-style policy calls. Ask what happens when the current policy invents a failure absent from the stored pairs: DPO has no training row for it until you refresh the data, while PPO can expose it only after a fresh rollout reaches its judge. If annotators keep disagreeing, more labels can quantify uncertainty. They won't turn a vague rubric into a training signal.
When the proxy lies
That release rehearsal is reward hacking in miniature. A policy exploits the scorer instead of getting better. The reward model is a proxy; optimize hard enough and the proxy becomes the target.
If the scorer overweights length or markdown, policy optimization will amplify those traits faster than answer quality. The visible symptom is rising proxy reward without a matching gain in held-out human preference.
| Hack | Symptom | Detection |
|---|---|---|
| Length gaming | Answers get steadily longer | Track mean response length |
| Sycophancy | Model agrees with a wrong user | Test with false user claims |
| Format exploitation | Everything wrapped in lists | Compare formatted vs plain scores |
| Confidence mimicry | Sounds sure on hard items | Calibration checks on known-hard questions |
Verbosity
Annotators sometimes treat length as quality. Predict what happens next: a padded P-7 explanation can beat a short, correct one, so the policy learns to pad if that correlation is in the data.
- Symptom: mean length climbs while helpfulness plateaus.
- Fix: length-matched preference evals, and explicit concise-versus-padded labels. Length penalties can also punish necessary detail, so validate them.
The 0.693 trap
If the policy starts equal to its reference, every DPO relative margin is zero and the loss starts near . That's expected. At zero logit, the gradient is , so the binary loss is not flat.
That is initialization, not evidence that a pair is weak. Ambiguous pairs still hurt. Conflicting labels on the same prompt can cancel in aggregate or teach arbitrary style. Diagnose from agreement, slices, and held-out behavior, not from initial loss.
1from math import exp, log
2
3def sigmoid(value: float) -> float:
4 return 1 / (1 + exp(-value))
5
6for logit in [0.0, 2.0]:
7 loss = -log(sigmoid(logit))
8 gradient = sigmoid(logit) - 1
9 print(f"logit={logit:.1f} loss={loss:.3f} gradient={gradient:.3f}")1logit=0.0 loss=0.693 gradient=-0.500
2logit=2.0 loss=0.127 gradient=-0.119- Symptom: loss stays near after real updates, and held-out preference doesn't move.
- Fix: confirm the policy is updating, then inspect contradictory labels, unresolved ties, prompt leakage, and missing coverage before blaming
beta.
A fresh DPO run starts near loss 0.693 with a pair gradient of about -0.5. Does that prove the pair is ambiguous or uninformative?
Answer
No. A policy initialized from its reference starts with zero relative margin, so 0.693 is expected and the directional gradient is strong. Suspect ambiguity only when training stays there without held-out improvement, then inspect conflicting labels, leakage, and coverage.
A release gate that actually uses humans
Release asks a simpler question: did behavior improve within the tested drift budget? Tie proxy improvement to behavior you care about:
- Hold out human eval. Higher RM score has to win more often with people.
- Track auxiliaries: length, refusal rate, formatting drift, calibration.
- Watch KL and stop when you leave the tested drift budget.
- Refresh the scorer when current outputs leave its training distribution.
With the release-candidate numbers from the opening, predict the gate before running it: proxy reward should improve, human preference should fail, and promotion should be blocked.
1metrics = {
2 "proxy_reward_delta": 0.31,
3 "held_out_human_win_delta": -0.04,
4 "mean_length_delta_tokens": 48,
5}
6ready = metrics["held_out_human_win_delta"] > 0 and metrics["mean_length_delta_tokens"] < 20
7
8print(f"proxy_improved={metrics['proxy_reward_delta'] > 0}")
9print(f"human_preference_improved={metrics['held_out_human_win_delta'] > 0}")
10print(f"release_ready={ready}")1proxy_improved=True
2human_preference_improved=False
3release_ready=False
Other failure signatures worth the same stop rule:
- DPO loss stays near after confirmed updates: audit labels, ties, leakage, and coverage rather than treating init loss as a diagnosis.
- DPO improves tone but still grants admin: the pair set rewarded style, not the P-7 refusal. Add targeted safety pairs and policy-specific evals.
- PPO generations get repetitive while reward rises: freeze promotion, inspect reward scale and KL, then refresh evaluation together.
Online DPO and iterative alignment
Vanilla DPO is offline: it trains on a fixed set of chosen and rejected responses.[3] Ask what happens when the policy improves past those rows: old comparisons describe a model you no longer have, so they may no longer contain hard negatives.
Later work studies the online setting: sample fresh responses from the current policy, label them with humans or a preference model, then apply an IPO-style or related pairwise update on the new comparisons.[5]

The loop is:
- Sample two or more candidates from the current policy.
- Ask humans or a preference model which is better.
- Store a new
(prompt, chosen, rejected)triple. - Run a DPO-style update with whatever reference-policy rule the method uses.
- Repeat so the data follows the model.
There isn't one canonical "online DPO" algorithm, and reference-update choices differ. The family resemblance is what matters: refresh preference data on-policy, keep a pairwise loss, skip a full PPO stack.[5] You get less distribution mismatch and harder negatives over time, without necessarily standing up a critic.
Nearby methods, not drop-in replacements
These methods make different data contracts. Start with the signal you can reliably collect: a chosen/rejected pair, a binary label, or a task verifier. Nearby objectives relax the pair requirement, change the loss, or drop the reference. One popular method in this table isn't a DPO variant at all.
| Method | What changes |
|---|---|
| IPO (Identity Preference Optimization) | Replaces DPO's log-sigmoid with a squared loss on the preference margin, so the margin isn't driven without bound when labels look nearly deterministic.[6] |
| KTO (Kahneman-Tversky Optimization) | Drops pairs. Binary thumbs-up/down on individual outputs is enough.[7] |
| ORPO (Odds Ratio Preference Optimization) | Folds SFT and preference alignment into one stage and drops the separate reference model.[8] |
| SimPO (Simple Preference Optimization) | Drops the reference and uses length-normalized average log-probability as the implicit reward, plus a target margin . In the authors' setups, around 2.0 to 2.5 and around 0.5 to 1.5 were typical.[9] |
| GRPO (Group Relative Policy Optimization) | Online RL, introduced in DeepSeekMath: sample a group of outputs for one prompt, normalize rewards inside the group, and skip a learned critic. DeepSeek-R1 is a later, verifier-heavy reasoning example.[10][11][12] |
IPO, KTO, ORPO, and SimPO still live in the offline preference family. GRPO is a different branch. Process reward models (PRMs) score intermediate steps rather than only the finished answer; that's adjacent credit assignment, and RLVR & Verifiable Rewards treats it properly.[13]
For subjective assistant behavior from fixed pairs, start with DPO. Binary logs without pairs point to KTO. Tight memory makes ORPO and SimPO attractive because they skip a frozen reference. When DPO overfits near-deterministic labels, IPO is the bounded-margin test. When a task has a checkable verifier, online RL such as GRPO is the method to evaluate, not another DPO flag.
There is one edge case worth predicting before the code: if every verifier gives a group the same reward, normalization has no direction and every relative advantage should be zero.
1from statistics import mean, pstdev
2
3def group_advantages(rewards: list[float]) -> tuple[float, float, list[float]]:
4 center = mean(rewards)
5 scale = pstdev(rewards)
6 if scale == 0:
7 return center, scale, [0.0 for _ in rewards]
8 return center, scale, [(reward - center) / scale for reward in rewards]
9
10reward_groups = {
11 "mixed": [1.0, 0.0, 0.5, 1.0],
12 "all_equal": [1.0, 1.0, 1.0, 1.0],
13}
14
15for name, rewards in reward_groups.items():
16 center, scale, advantages = group_advantages(rewards)
17 print(
18 f"{name}: mean={center:.3f} std={scale:.3f} "
19 f"advantages={[round(value, 3) for value in advantages]}"
20 )1mixed: mean=0.625 std=0.415 advantages=[0.905, -1.508, -0.302, 0.905]
2all_equal: mean=1.000 std=0.000 advantages=[0.0, 0.0, 0.0, 0.0]The zero-variance guard is load-bearing. If every sampled answer gets the same reward, the group has no relative signal. TRL reports frac_reward_zero_std, the fraction of generation-batch samples whose reward standard deviation is zero. A high value means the rewards aren't distinguishing answers, so inspect the verifier or the sampling before assuming GRPO is learning from useful comparisons.[12]
What comes after pairwise human labels
Human comparisons don't scale to every new failure. Constitutional AI and RLAIF (Reinforcement Learning from AI Feedback) write the rules down as principles, then use a model to critique, revise, and rank against those principles, so you need fewer repeated harmlessness labels.[14] The next chapter is that pipeline, plus red teaming to see where the principles fail.
The dependence doesn't go away. A constitution is only as good as the judge that applies it. Held-out humans still decide whether "cite P-7 and escalate" survived contact with a real user.