← Home

Pooled diversity is the wrong thing to log

Shreshth Rajan, June 2026. · Code and tool: SRAG

If output diversity is the number you watch, it won't show you a model collapsing.

With no answer key you can still train a model on its own output. Sample eight answers and reward each one for matching the other seven. Test-time RL works roughly this way.

The cheat is obvious: give the same answer eight times and you score 100%, right or wrong. I wanted to know whether you'd see it happen.

I ran GRPO on coding problems with Qwen2.5-Coder-1.5B. Eight answers per problem, and instead of running the tests I scored each answer by how many of the other seven matched it.

That reward is highest when all eight agree, so agreement becomes the goal and correctness never enters into it. GRPO also scales each update by how spread out the rewards are, so once the eight agree the spread is zero, the gradient vanishes, and the model stays where it landed.

# the reward, and the gate the gradient sees
def consensus_rewards(samples):                 # eight answers to one problem
    key = [canonical(s) for s in samples]       # ignores formatting differences
    n   = Counter(key)
    return [(n[k] - 1) / (len(samples) - 1) for k in key]

adv = (r - r.mean()) / (r.std() + 1e-6)         # all rewards equal -> std 0 -> no gradient

Reward climbed to 1.0. On held-out problems with real tests, the first answer was still right 31% of the time. But the chance that at least one of eight answers was right fell from 91% to 46%.

Agreement reward rises to 1 while the chance one of eight answers is right halves.
The agreement reward saturating at 1.0, against the chance that one of eight held-out answers is right, and the chance that the single best one is.

The model hadn't gotten worse at coding. It had stopped producing different answers, so its eight tries were now one answer repeated eight times.

I was logging output diversity the whole run and it never moved.

That number throws every answer to every problem into one pile and measures the variety of the pile. The total is two things added together: how much the eight answers to one problem differ from each other, and how much one problem's answers differ from another problem's.

$$H(Y) \;=\; \underbrace{H(Y\mid X)}_{\text{within a problem}} \;+\; \underbrace{I(Y;X)}_{\text{between problems}}$$

Collapse only kills the first part. A sorting function and a string parser still look nothing alike. And the second part is most of the total, so the number you're watching barely moves. Variety within a problem went from 0.97 to zero. Pooled variety sat at 0.78 the whole time.

Per-problem answer variety collapses to zero while pooled variety stays flat.
Variety within a problem against pooled variety, on the same rollouts.

The number isn't noisy. It's measuring the wrong half of the total, and a single-best-answer score fails for the same reason, since the best answer is the one thing collapse leaves alone.

The fix costs nothing, because you already have the samples. Group the answers by problem, measure variety inside each group, and average.

pooled    = entropy(Counter(canonical(s) for g in groups for s in g))       # blind
perprompt = mean(entropy(Counter(canonical(s) for s in g)) for g in groups) # sees it

It warns you early, too. Variety within a problem turned over in the first few steps, while the held-out eight-answer score was still at 91%. Pooled variety showed nothing for another twenty.

One correction I owe. Of the four things I was watching, one did catch it: reward spread going to zero is the same event, and GRPO computes it every step anyway. The dashboard wasn't blind. The diversity number was the one reading that missed it.

If you train a model on its own agreement, log variety per problem rather than variety overall.

Setup. GRPO on MBPP with Qwen2.5-Coder-1.5B, LoRA rank 128, eight samples per prompt, held-out pass@k scored by real unit tests at temperature 0.6. The no-anchor reward is soft self-consensus over AST-canonicalized code; the control reward is unit-test pass rate; the runs are otherwise identical. Restoring the test-based reward keeps pass@k flat, so the self-grading is the whole cause. One run, eleven held-out problems: the effect is large and one-directional, but the exact warning time isn't reliable, since eleven problems jump around step to step. If pooled variety ever falls as much as per-problem variety, I'm wrong. Monitor and experiment, one command to reproduce: github.com/ShreshthRajan/SRAG.


References

  1. Zuo et al. TTRL: Test-Time Reinforcement Learning. arXiv:2504.16084 (2025).
  2. Shafayat et al. Can Large Reasoning Models Self-Train? arXiv:2505.21444 (2025).
  3. Yue et al. Does Reinforcement Learning Really Incentivize Reasoning Capacity in LLMs Beyond the Base Model? arXiv:2504.13837 (2025).
  4. Wu et al. The Invisible Leash: Why RLVR May Not Escape Its Origin. arXiv:2507.14843 (2025).
  5. Yu et al. DAPO: An Open-Source LLM Reinforcement Learning System at Scale. arXiv:2503.14476 (2025). Patches the same zero-spread case by resampling.
  6. Cui et al. The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models. arXiv:2505.22617 (2025).
  7. Shao et al. DeepSeekMath. arXiv:2402.03300 (2024). Introduces GRPO.
  8. Chen et al. Evaluating Large Language Models Trained on Code. arXiv:2107.03374 (2021). Defines the unbiased pass@k estimator.
  9. Cover and Thomas. Elements of Information Theory. Wiley (2006). Chain rule for the split above.
  10. Shumailov et al. AI Models Collapse When Trained on Recursively Generated Data. Nature 631, 755 (2024).