Skip to content

feat(grpo): add RLSD self-distilled advantage reweighting#9758

Merged
hjh0119 merged 4 commits into
modelscope:mainfrom
qychen2001:main
Jul 17, 2026
Merged

feat(grpo): add RLSD self-distilled advantage reweighting#9758
hjh0119 merged 4 commits into
modelscope:mainfrom
qychen2001:main

Conversation

@qychen2001

Copy link
Copy Markdown
Contributor

PR type

  • Bug Fix
  • New Feature
  • Document Updates
  • More Models or Datasets Support

PR information

Introduce RLSD (Self-Distilled RLVR) token-level advantage reweighting for GRPO, enabled via --advantage_reweight rlsd. The per-sequence advantage is redistributed inside each trajectory using the teacher-vs-student logprob gap as a sign-aware, clipped multiplicative weight, without flipping the reward sign. The teacher reuses the OPSD self-distillation forward (current policy conditioned on a privileged teacher_prompt column), and a frozen local teacher via --teacher_model is also supported.

  • add apply_rlsd_reweight in rl_core/advantage.py
  • add RLSD args and lambda warmup/decay schedule to the GRPO trainer
  • add _check_rlsd fail-fast validation in RLHFArguments
  • add unit tests in tests/train/test_rlsd_reweight.py

src: https://github.com/iie-ycx/RLSD/tree/main
paper: https://arxiv.org/abs/2604.03128

Introduce RLSD (Self-Distilled RLVR) token-level advantage reweighting for
GRPO, enabled via `--advantage_reweight rlsd`. The per-sequence advantage is
redistributed inside each trajectory using the teacher-vs-student logprob gap
as a sign-aware, clipped multiplicative weight, without flipping the reward
sign. The teacher reuses the OPSD self-distillation forward (current policy
conditioned on a privileged `teacher_prompt` column), and a frozen local
teacher via `--teacher_model` is also supported.

- add `apply_rlsd_reweight` in rl_core/advantage.py
- add RLSD args and lambda warmup/decay schedule to the GRPO trainer
- add `_check_rlsd` fail-fast validation in RLHFArguments
- add unit tests in tests/train/test_rlsd_reweight.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements RLSD (Self-Distilled RLVR) token-level advantage reweighting for GRPO. It introduces the validation checks, configuration arguments, the core reweighting algorithm, trainer integration, and comprehensive unit tests. The review feedback highlights a critical bug where NaN/inf values in padded positions can propagate and corrupt the training loss, and a medium issue where missing teacher logprobs can cause a silent fallback to standard GRPO instead of raising an error.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +321 to +331
mask = completion_mask
base_bt = base_advantages.unsqueeze(1).expand_as(mask)
delta = (teacher_per_token_logps.detach() - policy_per_token_logps.detach()) * mask
sign_a = torch.sign(base_bt)
weights = torch.exp(sign_a * delta) * mask
clipped = torch.clamp(weights, min=1.0 - clip_range, max=1.0 + clip_range)
reweight = (1.0 - lam) + lam * clipped
if negative_only:
seq_neg = (base_advantages < 0).float().unsqueeze(1)
reweight = seq_neg * reweight + (1.0 - seq_neg)
return base_bt * reweight.detach() * mask

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Bug: If teacher_per_token_logps or policy_per_token_logps contains NaN or inf (e.g., -inf in padded positions), the subtraction teacher_per_token_logps - policy_per_token_logps will produce NaN or inf. Multiplying by mask (which is 0 at padded positions) still results in NaN because NaN * 0 = NaN in PyTorch. This NaN then propagates through torch.exp, torch.clamp, and the final multiplication, causing the entire returned advantage tensor (and consequently the training loss) to become NaN. To prevent this, use torch.where to mask the logprob difference before any exponential or clamp operations. Additionally, cast seq_neg to reweight.dtype instead of .float() to support mixed-precision/half-precision training seamlessly.

Suggested change
mask = completion_mask
base_bt = base_advantages.unsqueeze(1).expand_as(mask)
delta = (teacher_per_token_logps.detach() - policy_per_token_logps.detach()) * mask
sign_a = torch.sign(base_bt)
weights = torch.exp(sign_a * delta) * mask
clipped = torch.clamp(weights, min=1.0 - clip_range, max=1.0 + clip_range)
reweight = (1.0 - lam) + lam * clipped
if negative_only:
seq_neg = (base_advantages < 0).float().unsqueeze(1)
reweight = seq_neg * reweight + (1.0 - seq_neg)
return base_bt * reweight.detach() * mask
mask = completion_mask.to(teacher_per_token_logps.dtype)
base_bt = base_advantages.unsqueeze(1).expand_as(mask)
raw_delta = teacher_per_token_logps.detach() - policy_per_token_logps.detach()
delta = torch.where(mask.bool(), raw_delta, torch.zeros_like(raw_delta))
sign_a = torch.sign(base_bt)
weights = torch.exp(sign_a * delta) * mask
clipped = torch.clamp(weights, min=1.0 - clip_range, max=1.0 + clip_range)
reweight = (1.0 - lam) + lam * clipped
if negative_only:
seq_neg = (base_advantages < 0).to(reweight.dtype).unsqueeze(1)
reweight = seq_neg * reweight + (1.0 - seq_neg)
return base_bt * reweight.detach() * mask

Comment on lines +266 to +267
use_rlsd = (self.advantage_reweight == 'rlsd' and grpo_batch.teacher_per_token_logps is not None)
if use_rlsd:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Medium Issue: If the user explicitly configures --advantage_reweight rlsd but forgets to provide a teacher_prompt column in the dataset or fails to configure a teacher, grpo_batch.teacher_per_token_logps will be None. In this case, use_rlsd silently evaluates to False, and the trainer falls back to standard GRPO without any warning or error. This silent failure makes it very hard for users to debug why RLSD is not active. We should raise a clear ValueError if RLSD is requested but the required teacher logprobs are missing.

            if self.advantage_reweight == 'rlsd':
                if grpo_batch.teacher_per_token_logps is None:
                    raise ValueError(
                        "advantage_reweight='rlsd' is enabled, but teacher_per_token_logps is None. "
                        "Please ensure that your dataset contains a 'teacher_prompt' column and that "
                        "the teacher model or self-distillation is correctly configured."
                    )
                use_rlsd = True
            else:
                use_rlsd = False
            if use_rlsd:

qychen2001 and others added 2 commits July 17, 2026 14:22
Add the new RLSD (Self-Distilled RLVR) CLI parameters to the English and
Chinese command-line references: advantage_reweight, rlsd_lambda,
rlsd_reweight_clip_range, rlsd_lambda_warmup_steps, rlsd_lambda_decay_steps
and rlsd_negative_only, including defaults, valid ranges, the reweight
formula, enable requirements and the two teacher modes.
Added separate jobs for trusted and external code reviews in GitHub Actions workflow.
@hjh0119
hjh0119 merged commit ffb41eb into modelscope:main Jul 17, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants