huggingface / huggingface/trl

Add UP (Unbounded Positive) loss as a GRPO loss_type

Open
#6,407 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
19.3k
Forks
3k
Avg merge
1d 20h
Merged PRs (30d)
194

Description

### Feature request

I'd like to add a `loss_type="up"` option to GRPO, implementing the Unbounded Positive (UP) objective from https://huggingface.co/papers/2607.06987 (Fan et al., ByteDance Seed).

UP routes tokens by the sign of the advantage (Eq. 14 in the paper). For tokens with positive advantage, the importance ratio is replaced with a self-anchored one, `π_θ / sg(π_θ)`. Its forward value is exactly 1 and its gradient is the plain REINFORCE gradient `Â ∇log π_θ`, with no dependence on `π_old`, so positive updates never get cut by the trust region. Tokens with non-positive advantage keep the normal clipped surrogate, unchanged.

The core of the positive branch is two lines (the rest of the change is dispatch, metrics, and tests):
```

anchored = per_token_logps - per_token_logps.detach()
loss = -(anchored.exp()) * advantages # positive-advantage branch
```

### Motivation

TRL already exposes several paper losses through the same loss_type dispatch (cispo, sapo, luspo, vespo), each as a small branch in _compute_loss, and UP slots in the same way. Its negative side is literally the existing dapo clipped surrogate, so most of the code is already there. A side effect is that epsilon_high becomes inert for this loss: with the positive side unbounded, the upper clip can never bind (the paper's UP-DAPO variant simply drops it). The branch doesn't currently warn about that, and I can surface a warning if you'd prefer.

Testing is also cheap here. The positive branch is gradient-equivalent to REINFORCE, so an exact gradient-identity test covers it, not just end-to-end smoke runs.

### Your contribution

Working branch: https://github.com/yashb98/trl/tree/feat/grpo-up-loss (diff vs main: https://github.com/huggingface/trl/compare/main...yashb98:trl:feat/grpo-up-loss). It adds:

- loss_type="up" in GRPOConfig (docstring) plus the _compute_loss branch, with DAPO-style global-token aggregation
- clip metrics logged on the negative side only, since the positive side has nothing to clip
- importance_sampling_level="sequence" support, which maps to the paper's UP-GSPO variant (Eq. 16)
- NotImplementedError on the Liger path for now
- a paper_index.md entry

Tests cover the gradient identity vs REINFORCE for positive advantages (including a check that the result ignores a stale old_per_token_logps), an exact match with dapo for non-positive advantages, and "up" added to the parametrized loss-type test. They pass locally (CPU runners), and the same branch ran a 200-step-per-arm GPU A/B end to end, so the loss has been exercised in a real training loop, not just unit tests.

That A/B was on my own box (a single GB10): Qwen3-0.6B on GSM8K, 200 steps, dapo vs up, off-policy with num_iterations=4, one seed per arm. The mechanism checks out: dapo's positive-side clip fraction stays nonzero across the run while up never clips positive tokens (by construction there is no ratio to clip), and up's entropy rises through training (last-10-step average about 1.87 vs 0.43 for dapo, both arms starting at 0.47), which is the direction the paper's Fig. 3(a) predicts, with comparable grad norms. Reward did not beat dapo inside 200 steps (about 0.31 vs 0.60 on the last 10 steps). The paper only reports gains at 8 to 14B (plus a 30B MoE and a VLM) and I can't test that scale on this hardware, so I'd treat the run as a mechanism check, not evidence about reward either way. I can share the exact training script.

4-panel A/B: positive-side clip fraction, policy entropy, grad norm, reward (dapo vs up, Qwen3-0.6B, GSM8K, 200 steps)

Image

Reward panel included for completeness; see the scale caveat above.

One design question I'd want your take on: the positive branch replaces the IS ratio with the anchored one, so as far as I can tell the ratio-based vLLM logprob-mismatch corrections (the truncate modes) would not act on positive tokens, while the default sequence_maskking happens after the loss dispatch. I can document that or add a warning on thecombination, whichever you prefer.

The branch is ready; happy to open a PR.

Contributor guide

Open the contributing guide

Research direction

Start with GRPOConfig and the _compute_loss dispatch, then inspect the existing dapo, cispo, sapo, luspo, and vespo branches and the loss-type tests. Review paper_index.md and the existing metrics and importance-sampling handling. Done means UP supports the described branches and sequence mode, has the stated tests and metrics behavior, and preserves the requested Liger limitation.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
machine-learning
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.