AO
Back
Anthropic · MLE / Research

Debug GRPO / RL Training Code

Debugging2× totalLast reported March 2026
By AceOffer · First reported October 2025 · 2× across 2300+ candidate reports

The Round

Time budget

Not stated in the reports.

Environment

Not stated: no report names the framework or the editor. The reports mention multinomial sampling on logits and model log-probs; the code on this page uses PyTorch as our illustration.

What they show you

A simple GRPO training script (one report's words) with bugs in it. What the reports show of it: logits passed to multinomial sampling, a normalized advantage divided by a standard deviation, a ratio built from model_logprob and old_logprob, and ratio clipping. No report gives its length or structure. Candidates call the round "RL Fundamentals"; "technical deep dive" is our catalog label.

What you can use
Not stated in the reports
Reported Outcomes (2 candidates)
2 unknown

What You Walk Out With

  • The bugs fixed: one report names two (a softmax before sampling, an epsilon on the advantage std); another says there are three

  • Answers to the RL follow-ups: the log-difference ratio, why and when the ratio is clipped, and what clipping does

  • An answer to whether the ratio in this code should in theory be 1, and why; then the ratio printed (in one report it was not 1) and the cause debugged

Problem Statement

Asked in the round candidates call "RL Fundamentals". You get a simple GRPO (Group Relative Policy Optimization) training script, debug it, then answer follow-up questions on RL basics. One candidate (October 2025) first hit a nan error and found two bugs: the code passed raw logits to multinomial sampling without a softmax, and the normalized advantage divided by the standard deviation without an epsilon. Another candidate (March 2026) says there were three bugs, all easy to spot for anyone who knows GRPO, but does not name them; no report names the third.

After the fixes the first candidate was asked whether the model still trains if the ratio is computed as model_logprob - old_logprob, why the ratio is clipped, when it gets clipped and what clipping does, and then whether the ratio in this code should in theory be 1, and why; printed, it was not 1, and they had to debug why. The second candidate called the follow-ups basic except one: finding why the run is not strictly on-policy, that is, why the ratio is not always 1, which took them a long time.

The version may differ: an August 2026 poster says the GRPO debugging question they got was different from, and more complicated than, the one posted on the forum.

Prerequisites

Brush up on these before sitting the round.
Math
softmaxlog-likelihoodpolicy-gradient theorem (sketch)importance-sampling ratioKL divergence (for the clip-vs-KL follow-up if it lands)
Libraries
PyTorch, our illustrative framework (multinomial, log_softmax, gather)torch.distributions.Categorical (cleaner than raw multinomial)
Concepts
PPO clip objectiveGRPO group-relative advantageon-policy vs. off-policyrollout vs. update batchmini-epoch (PPO-style multi-step update on one batch)numerical stability (epsilon, log-sum-exp)trust region (clip as a soft trust-region constraint)

Canonical Solution

Synthesized from candidate reports — the approach interviewers expect.

No report describes a model answer; this is our approach. (1) Find the multinomial call and sample from softmax(logits), or from Categorical(logits=logits). (2) Find the advantage normalization and add an epsilon to the std; also make sure every group has more than one sample, because torch's default std is the unbiased one (correction=1), which is NaN for a single value whatever the epsilon (or use correction=0). (3) No report names the third bug, so check the rest of the loss against the GRPO objective: the ratio should be exp(new_logprob − old_logprob), the clip should act on that ratio, and any KL term should compare with the reference policy. Then be ready to explain why the printed ratio is not 1 in nominally on-policy code (see the follow-ups).

Process Playbook

The process the interviewer rewards — distilled from candidate reports.
01
Run it and read the first error
Why: One candidate's first symptom was a nan error. In PyTorch, logits with a negative entry passed to torch.multinomial raise an error whose message mentions inf, nan or element < 0, while a zero std gives real NaN values; either can be what a candidate calls a nan error.
02
Trace the data flow: logits, sampling, rewards, group advantages, ratio, clipped loss
Why: Our suggestion. Both named fixes sit on that path, and a NaN anywhere spreads to every later number, so fix the NaN sources before you read the ratio.
03
Check every formula against the GRPO objective, not only the ones that crash
Why: One report says there are three bugs and names none; the other names two that crash. A bug that does not crash only shows up by reading the code or printing values.
04
Print the ratio at the first update on a fresh batch
Why: In one report the interviewer asked whether the ratio should be 1 in this code, then had the candidate print it: it was not 1. At the first update on a fresh batch the correct ratio is exactly 1; if it is not, the old and new log-probs come from different computations.
05
Prepare the follow-ups before the round
Why: The candidate who expected to fail struggled in the follow-ups, not the fixes; the other found them basic except the on-policy question. Without RL background, one commenter's advice was to ask the recruiter for a different topic.

Bug Catalogue

The bugs Anthropic plants are drawn from a small recurring catalogue. Recognize them by signature, not by reading line-by-line.
#1Reported: sampling from raw logits
Signature: Sampling raises a RuntimeError (on CPU: probability tensor contains either inf, nan or element < 0) as soon as a row of logits has a negative entry, so a candidate may well call it a nan error even though no tensor holds NaN. If every logit in a row is finite and non-negative and the row sum is above zero, it runs and silently samples in proportion to the raw logits instead of the softmax policy.
Root cause: In PyTorch (our illustration; no report names the framework), torch.multinomial expects finite, non-negative weights with a positive sum in each row, not logits; the weights need not sum to 1. Logits can be negative and are not probabilities, so passing them in is either an immediate error or a silently wrong distribution.
Fix: Sample from F.softmax(logits, dim=-1), or more cleanly torch.distributions.Categorical(logits=logits).sample().
# Before:
action = torch.multinomial(logits, 1)

# After:
probs = F.softmax(logits, dim=-1)
action = torch.multinomial(probs, 1)

# Cleaner:
action = torch.distributions.Categorical(logits=logits).sample()
#2Reported: no epsilon on the advantage std
Signature: NaN advantages when every reward in a group is exactly equal (std = 0, so 0/0). Near-equal rewards do not blow up: normalization is scale-invariant, so [1, 1, 1 + 1e-6] becomes about [−0.71, −0.71, 1.41] (population std) or [−0.58, −0.58, 1.15] (torch's default unbiased std). The effect is that tiny reward differences, even float noise, are stretched to unit-size advantages.
Root cause: Group-relative normalization divides by the std with no epsilon. Torch's default std is unbiased (correction=1), so a group of one sample is NaN as well, and an epsilon does not help there; NumPy's default (ddof=0) gives std 0 for one sample. An epsilon also damps differences far smaller than itself: with eps = 1e-8, [5, 5, 5 + 1e-12] gives advantages below 1e-4 instead of unit size.
Fix: Add a small epsilon to the std. Keep every group larger than one sample, or use correction=0.
# Before:
advantage = (returns - returns.mean()) / returns.std()

# After:
advantage = (returns - returns.mean()) / (returns.std(correction=0) + 1e-8)
#3Our guess, not reported: ratio as a log-difference
Signature: The ratio prints near 0 instead of near 1. Training still improves: positive-advantage samples get the plain policy-gradient update (A·∇logπ) until the log-difference passes 1.2, while negative-advantage samples sit on the constant clipped branch (0.8·A) and get no gradient until it passes 0.8. Near d = 0, which is where a single update per batch stays, only positive-advantage samples teach the model; a negative-advantage sample contributes only once d passes 0.8 (1 − ε). The clip no longer acts as a trust region.
Root cause: The importance ratio computed as new_logprob − old_logprob instead of exp(new_logprob − old_logprob). Provenance: no report names the third bug. We list this one only because the interviewer in one report asked about this exact formula, and asked it after the fixes, as a what-if, which suggests the code did not contain it. Treat it as our guess.
Fix: Compute the ratio as the exponential of the log-prob difference. At the first update on a fresh batch it is exactly 1; later updates on the same batch can move it.
# Before:
ratio = new_logprob - old_logprob   # the log-ratio, not the ratio

# After:
ratio = torch.exp(new_logprob - old_logprob)
# At the first update on a fresh batch the ratio should be exactly 1:
# assert torch.allclose(ratio, torch.ones_like(ratio))

Follow-up Arc

Probes the interviewer escalates with — each one has a pre-extracted canonical answer. Click to reveal.
#1WarmupAfter the bugs are fixed

If the ratio were computed as model_logprob − old_logprob (the log-difference rather than its exponential), could the model still train? Why or why not?

#2CoreAfter the log-difference question

Why do we clip the ratio in PPO/GRPO, and when does a ratio actually get clipped?

#3CoreAfter the clipping question

What happens to training when the ratio gets clipped?

#4StretchAfter the clipping questions

In this code, should the ratio in theory always be 1? Why? Print it: it is not 1. Debug why.

Observed Variants

The same question shows up in different shapes — base, with deep dives, with extra constraints. Be ready for any of these.
  • October 2025: a nan error first, then two named bugs (no softmax before multinomial sampling; no epsilon on the advantage std), then RL follow-ups

  • March 2026: three bugs, not named; the hard follow-up was why the ratio is not always 1

Pitfalls

  • Passing logits straight to sampling. In PyTorch (our illustration), torch.multinomial takes weights that need not sum to 1: a negative logit raises an error (its message mentions inf, nan or element < 0). It runs only when every weight in the row is finite and non-negative and the row sum is above zero, and then it samples in proportion to the raw logits, not the softmax policy.

  • Normalizing advantages without an epsilon. A group whose rewards are exactly equal gives 0/0 = NaN. Torch's default std is the unbiased one (correction=1), so a one-sample group is NaN too, epsilon or not: keep groups larger than one or use correction=0.

  • Thinking near-equal rewards blow up the advantages. They do not: (r − mean)/std is scale-invariant, so rewards [1, 1, 1 + 1e-6] normalize to about [−0.71, −0.71, 1.41] with the population std, or [−0.58, −0.58, 1.15] with torch's default unbiased std, and no entry can exceed √(n−1) in size (population std). The real effect is that tiny differences, even float noise, become unit-size advantages; an epsilon damps only differences much smaller than itself (with eps = 1e-8, rewards [5, 5, 5 + 1e-12] give advantages below 1e-4).

  • Computing the ratio as the log-difference instead of its exponential: training still improves, but near d = 0 only above-average samples get a gradient (below-average ones only once d passes 1 − ε) and the clip no longer acts as a trust region (see the first follow-up).

  • Assuming the ratio is 1 without printing it. At the first update on a fresh batch it is exactly 1 only if the old and new log-probs come from the same computation; the on-policy follow-up is about exactly this.

  • Answering 'why do we clip?' with only 'to prevent large updates'. Clipping is a trust-region heuristic that removes the incentive to move further once the ratio leaves [1−ε, 1+ε] in the direction the advantage favours. It does not bound KL(new ‖ old), which is why PPO has an adaptive-KL-penalty variant and the standard GRPO objective adds a separate KL term to a reference policy (some implementations drop it, so check whether this code has one). Whether a sample's gradient is zero depends on the advantage sign and which branch of the min() is active, not only on whether the ratio is outside the band.

Insider Notes

Candidates call this round "RL Fundamentals", and recruiters match candidates to it under that name. Two first-hand reports describe it, from October 2025 and March 2026. The October candidate had not seen it written up before; several later threads ask to trade for it.

The two candidates described the difficulty differently. The March 2026 candidate called the three bugs straightforward, spotted at a glance by anyone who knows GRPO, and the follow-ups basic, except one: working out why the run is not strictly on-policy, which took them a long time. The October candidate described the same kind of follow-ups as a run of questions in their blind spots and expected to fail. Another candidate was matched to this round although they had told the recruiter they had no RL background; a commenter's advice was that without that background, even knowing the question, you are unlikely to get through the follow-up discussion, and to ask the recruiter to switch topics.

In the October candidate's loop this round sat alongside a coding round (find the mode across distributed workers, with a median follow-up) and a coding-and-design round (a weighted data batcher with checkpointing).

What on this page is ours: no report names the third bug. The third entry in the bug catalogue (the ratio computed as the raw log-difference) is our guess, based only on the interviewer asking about that formula; since it was asked after the fixes, as a what-if, the code probably did not contain it. The cause of the ratio drift is not reported either. The follow-up answers, the playbook and the prerequisites are our analysis from standard PPO and GRPO results, not an interviewer's rubric.

Prepping for Anthropic with friends?

Send them this page. It is free to read, no account needed.

More Anthropic Questions

Free preview

Every question in the Anthropic catalog gets this depth

What you just read — canonical solution, follow-up arc, what passing candidates actually did — exists for all 49 Anthropic questions, refreshed monthly from new candidate reports.

$59/mo — or $50/mo with the 3-month pass · cancel anytime
Anthropic · MLE / Research · Debugging · 2× reportedLast reported March 2026
Is this helpful?