Skip to content

[Doc] Document the GRPOLoss masking strategy contract and add deterministic tests - #4340

Open
n-dlms wants to merge 2 commits into
pytorch:mainfrom
n-dlms:fix/grpo-masking-shape-contract
Open

n-dlms wants to merge 2 commits into
pytorch:mainfrom
n-dlms:fix/grpo-masking-shape-contract

Conversation

@n-dlms

@n-dlms n-dlms commented Sep 12, 2026

Copy link
Copy Markdown

Description

Documents the masking strategy contract of GRPOLoss in its docstring and adds a deterministic CPU test class that pins it down.

Context: issue #4227 reports a suspected shape mismatch between masking strategies. I reproduced the full matrix on CPU with trl-internal-testing/tiny-Qwen2ForCausalLM-2.5 and the Qwen/Qwen2.5-0.5B tokenizer, fabricating the response tokens so that no generation engine is required. Where a supported combination runs, masks are boolean tensors of shape (batch, T) over the full padded sequence, the distribution log probability has the same shape, and a per trajectory advantage of shape (batch, 1, 1) broadcasts cleanly. I could not reproduce a shape bug in the loss reduction itself.

What the docstring note now states, and what the tests assert:

  • tokens input: sft masks prompt positions using ("tokens", "prompt"), generic masks padding using the attention mask, and rlhf uses a caller supplied ("masks", "all_assistant_mask") when the tensordict carries one and raises a ValueError only when it does not. The tests compare the distribution mask against those expected tensors, so a drift in any selection fails in CI.
  • history input: the assistant mask is computed from the chat template. rlhf uses it directly and sft falls back to it whenever ("tokens", "prompt") is unavailable, which makes sft and rlhf select the same tokens in this mode.
  • a batch 2 history input with heterogeneous prompts and fabricated assistant responses runs through GRPOLoss.forward with a finite loss for all three strategies, and the unreduced loss keeps the (B, T, 1) token layout the advantage broadcasts against.

No behavior is changed: the docstring documents current behavior, and the two open contract questions from my comment on #4227 (whether sft in history mode should warn about the fallback, and what error rlhf with tokens input should suggest) remain for the maintainers to decide.

Correction to my earlier comment on #4227

In that comment I claimed the transformers wrapper history path fails at batch sizes greater than one. That was wrong, and the probe script I referenced was the cause: it built the History incorrectly, stacking prompt and response as separate conversation slots instead of concatenated messages, which produced a malformed (batch, 2, T) token tensor. With a correctly built History (per row History.from_chats, extended along the message dimension with dim=-1, then lazy stacked) the wrapper handles batch 2 with heterogeneous prompts fine, including the assistant masks, for all three strategies. The wrapper batch path is therefore not implicated in the skipped integration test as I suggested.

Test results

pytest test/llm/test_llm_objectives.py -k "TestGRPOLossMaskingContract or TestLosses"
13 passed in 42.73s

Motivation and Context

Fixes the documentation side of #4227 and pins the contract with deterministic CPU tests. The skipped vLLM integration test is left untouched: its path involves actual generated output, which the fabricated response cases here do not exercise, so validating that integration remains a follow-up before its skip could be lifted.

  • I have raised an issue to propose this change (required for new features and bug fixes)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds core functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (update in the documentation)
  • Example (update in the folder of examples)

Checklist

  • I have read the CONTRIBUTION guide (required)
  • My change requires a change to the documentation.
  • I have updated the tests accordingly (required for a bug fix or a new feature).

…nistic CPU tests

Issue 4227 reports a suspected shape mismatch between GRPOLoss masking
strategies. Local verification on CPU shows that every supported
combination of wrapper input mode and masking strategy produces boolean
(batch, T) masks and a finite loss, and that the failing combinations are
contract questions rather than shape bugs: rlhf with tokens input cannot
have an assistant mask, and sft with history input silently falls back to
the assistant mask.

This documents the full matrix in the GRPOLoss docstring and adds a
deterministic CPU test class that covers it with the tiny
trl-internal-testing checkpoint, fabricating the response tokens so no
generation engine is needed. The tests fail loudly if the masking
contract drifts.
@pytorch-bot

pytorch-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/rl/4340

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 16 Awaiting Approval

As of commit 9dc005c with merge base ed803e4 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 12, 2026
@github-actions github-actions Bot added Documentation Improvements or additions to documentation Objectives llm/ LLM-related PR, triggers LLM CI tests and removed Documentation Improvements or additions to documentation labels Sep 12, 2026

@vmoens vmoens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new CPU cases exercise the loss with supported input shapes, but the documentation rules out a supported tokens-mode RLHF input, and the scalar comparisons do not establish which tokens each strategy selects. Please address the inline comments before merging.

Suggested next steps:

  1. Correct the assistant-mask requirement and describe the intermediate shapes explicitly: masks and log probabilities are (B, T), log-weights are (B, T, 1), and advantages can be (B, 1, 1) per trajectory or (B, T, 1) per token for these batched inputs.
  2. Strengthen the existing cases with expected distribution masks and unreduced loss shapes. Cover tokens-mode RLHF with both an absent and a caller-supplied assistant mask. These checks can fit into the existing cases without adding another layer of redundant tests.
  3. In the history helper, remove tokenizer_kwargs={"chat_template_name": "qwen"} or use the top-level chat_template_name="qwen"; the history path constructs its own tokenizer kwargs, so the nested setting is ignored.

Further follow-ups: make the missing-assistant-mask error suggest supplying the mask or using history input, and investigate the original skipped vLLM integration with actual generated output. The fabricated-response tests do not establish that this integration is fixed, so validate that path before removing its skip and clarify the PR's replacement claim accordingly.

Validation on e0f3d7b: pytest test/llm/test_llm_objectives.py -k 'TestGRPOLossMaskingContract or TestLosses' passed all 13 selected tests on CPU with PyTorch 2.11.0 and Transformers 4.56.2. A separate probe confirmed caller-supplied assistant masks work in tokens mode. It also confirmed all six new cases still pass with SFT/generic swapped for tokens input and generic incorrectly using assistant masking for history input. GPU/vLLM integration was not run. I found no evidence here that warrants changing the loss math.

Comment thread torchrl/objectives/llm/grpo.py Outdated
Comment on lines +395 to +398
* ``tokens`` input: ``"sft"`` masks the prompt positions using ``("tokens", "prompt")``,
``"generic"`` masks padding positions using the attention mask, and ``"rlhf"`` requires
an assistant mask which is only computed in history mode, so ``"rlhf"`` with ``tokens``
input raises a ``ValueError``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Qualify the tokens-mode RLHF restriction

The tokens log-prob path preserves a caller-supplied ("masks", "all_assistant_mask"), and the RLHF distribution uses it. I verified that supplying a boolean response mask makes GRPOLoss(masking_strategy="rlhf") return a finite loss with exactly that mask. Please say this raises only when no assistant mask is available, and cover both missing and supplied masks in the tests. Tokens input itself is supported.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 9dc005c. The docstring now states that rlhf with tokens input uses a caller supplied assistant mask and raises only when it is absent, and the tokens rlhf case covers both sides: without a mask it raises, with a supplied boolean mask the distribution mask equals exactly that mask and the loss is finite.

Comment thread test/llm/test_llm_objectives.py Outdated
Comment on lines +1594 to +1600
sft = GRPOLoss(actor_network=wrapper, masking_strategy="sft")(td)
generic = GRPOLoss(actor_network=wrapper, masking_strategy="generic")(td)
assert torch.isfinite(sft.loss_objective)
assert torch.isfinite(generic.loss_objective)
# sft excludes the prompt positions, generic includes them, so with the
# same data and advantage the two losses must differ
assert sft.loss_objective.item() != generic.loss_objective.item()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Assert the expected token selection

Unequal scalar losses do not establish that either strategy selects the correct tokens. I swapped SFT and generic for tokens input and made generic use assistant masking for history input; all six new cases still passed. With the balanced per-trajectory advantages, the history equality check can also compare zero losses despite different token selections. Please assert the expected distribution masks: attention with prompt positions cleared for tokens-mode SFT, attention for generic, and the assistant mask for history-mode SFT/RLHF when prompt tokens are absent. Retain the forward checks and verify the unreduced (B, T, 1) loss shape to protect the shape contract.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 9dc005c. Each case now compares the distribution mask against the expected tensor: attention with prompt positions cleared for tokens sft, attention for generic, assistant mask for history sft and rlhf. The scalar loss equality case is removed in favor of those comparisons, and the parametrized history case also checks the unreduced (B, T, 1) shape with aggregation='none'. One note: the wrapper output masks arrive as int64 in the tokens path, so the comparisons cast both sides to bool; the history masks are already boolean.

Comment thread torchrl/objectives/llm/grpo.py Outdated
Comment on lines +404 to +406
In every supported combination the masks are boolean tensors of shape ``(batch, T)`` over
the full padded sequence and the distribution log probability has the same shape, so a per
trajectory advantage of shape ``(batch, 1, 1)`` broadcasts against them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3] Describe the log-weight dimension used for broadcasting

The advantage broadcasts against the (B, T, 1) log-weight produced by _log_weight, rather than directly against the (B, T) log probabilities or masks. Directly combining (B, 1, 1) with (B, T) would broadcast to (B, B, T). Please state the intermediate singleton dimension and the supported (B, 1, 1) per-trajectory / (B, T, 1) per-token advantage shapes explicitly; forward requires advantage and log-weight to have the same rank.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 9dc005c. The note now states the (B, T, 1) log weight that the advantage broadcasts against, the supported (B, 1, 1) per trajectory and (B, T, 1) per token advantage shapes, and the same rank requirement enforced by forward. Also fixed the history helper to pass chat_template_name to the wrapper constructor instead of the ignored nested tokenizer_kwargs. Both follow ups (the missing mask error message and validating the skipped vLLM integration with generated output) are acknowledged as separate work; the PR description no longer claims to replace that test.

…nd tests

The tokens input supports rlhf whenever the tensordict carries a caller
supplied assistant mask, so the docstring now states that the ValueError
only fires when the mask is absent, and describes the intermediate
shapes: masks and log probabilities are (B, T), the log weight is
(B, T, 1), and the advantage can be (B, 1, 1) per trajectory or (B, T, 1)
per token.

The tests now assert the expected distribution masks (prompt positions
cleared for tokens sft, attention for generic, assistant mask for history
sft and rlhf), cover tokens rlhf with an absent and with a caller
supplied mask, and check the unreduced (B, T, 1) loss shape. The
redundant loss equality case is replaced by the direct mask comparisons,
and the history helper passes chat_template_name to the wrapper
constructor instead of the ignored nested tokenizer_kwargs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Documentation Improvements or additions to documentation llm/ LLM-related PR, triggers LLM CI tests Objectives

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants