feat(rewards): add a safe five-component RLCR math reward#611
Open
toffee-desuwa wants to merge 3 commits into
Open
feat(rewards): add a safe five-component RLCR math reward#611toffee-desuwa wants to merge 3 commits into
toffee-desuwa wants to merge 3 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
As invited in #607, this PR contributes the reward implementation proposed there.
This patch adds
RLCRRewardFn, a single registered reward class implementing the five-component reward structure from Damani et al., “Beyond Binary Rewards: Training LMs to Reason About Their Uncertainty”:It also adds a runnable math example and focused behavioral tests.
Why one class returns five values
Trinity associates one reward function with a task. In the standard workflow, the returned reward dictionary is reduced with
sum(reward_dict.values()); it does not apply a separate per-component weight mapping there.RLCRRewardFntherefore validates a named weight mapping and returns five already weighted floats. This claim is limited to the standard reward-dictionary workflow; other scalar reward-shaping utilities in the repository have their own weighting behavior.Response and scoring contract
The final response must end with this case-sensitive chain, followed by whitespace only:
The class uses the final complete chain, parses only the final
<answer>payload, and requires finiteq ∈ [0,1]. A valid response returns:format = 1accuracy = abrier = 1 - (a - q)^2mean_confidence = qconfidence_one_or_zero = 1[q < 0.01 or q > 0.99]Each value is multiplied by its named configured weight before it is returned.
Malformed terminal format zeros format, accuracy, and Brier. The two small confidence auxiliaries are retained only when the final confidence field is independently and safely parsed. Parser/verifier exceptions or timeouts are treated as unknown: accuracy and Brier are zero, not “wrong answer.”
Intentional differences from the official reference code
The port preserves the published five-component reward shape but deliberately hardens numerical and dispatch behavior. These deviations are part of the compatibility claim; the official reference code is
reward_fns.py:NaN,Inf,-Inf) instead of letting it enter reward arithmetic.qis malformed or cannot be parsed safely.qinstead of clamping it into the auxiliary reward path.sourcekwarg is accepted and ignored (the reference implementation dispatches the whole batch fromsource[0]).<think>…</think><answer>…</answer><analysis>…</analysis><confidence>q</confidence>chain with only trailing whitespace.<think>opener. The referenceformat_reward(tabc) regex begins with.*?</think>, so it awards the format point to a response that has only a</think>closer and no<think>opener (for examplework</think><answer>…</answer><analysis>…</analysis><confidence>q</confidence>matches). This port requires<think>to be present and balanced, and scores a missing opener as zero format, accuracy, and Brier..*?prefix, so extrathink/answer/analysis/confidencetags that appear earlier (including nested or unbalanced ones inside the reasoning region, for example<think>x<answer>nested</answer></think>before a valid terminal chain) are absorbed by the prefix and still receive the format point. This port scans all reserved tags with a stack discipline and scores any stray, nested, or crossed reserved tag anywhere as zero format, accuracy, and Brier.The example's weights are runnable defaults, not a frozen experimental prescription.
Files changed
trinity/common/rewards/rlcr_reward.pytrinity/common/rewards/__init__.pytests/common/rewards/test_rlcr_reward.pyexamples/rlcr_math/rlcr_math.yamlexamples/rlcr_math/README.mdTest evidence
Local focused execution used minimal in-process stubs only for dependencies missing from the current Windows environment. Packaged commit:
d350ef09fdd1fc31928fda98911817da21089e86. Exact local commands and results (paths shown relative to the repository checkout root<checkout>):ray/word2numberstubs —python <checkout>/run_focused_with_stubs.py→ 62 passed.pre-commit run --files <checkout>/trinity/common/rewards/rlcr_reward.py <checkout>/trinity/common/rewards/__init__.py <checkout>/tests/common/rewards/test_rlcr_reward.py <checkout>/examples/rlcr_math/rlcr_math.yaml <checkout>/examples/rlcr_math/README.md→ all passed, including Black 23.7.0, isort 5.12.0, flake8 6.1.0, and mypy 1.7.0.ruff checkon the changed Python files (All checks passed!),python -m compileall -qon the implementation and tests (exit 0), a PyYAML parse ofexamples/rlcr_math/rlcr_math.yaml(YAML_OK), andgit diff --check <base>..<head>(exit 0, no output).[dev],[data], and[tinker]extras, CPU-only) on the patched checkout of9c29c18b: 358 passed / 82 failed / 5 skipped / 2 errors / 176 subtests passed (38 min). The RLCR reward tests pass natively: 62/62 inside the full run and in a standalone rerun.9c29c18bbaseline in the same venv (git stashswap, same paths). Zero failures are attributable to this patch. All of them require GPU training stacks (fsdp2/megatron), sglang, real model weights, or ray timing not available in a CPU-only environment; one timing-flaky scheduler test was observed on both trees. Node-by-node attribution list is retained by the submitter.tests/common/vllm_test.pywas excluded: it callsget_model_path()at collection time and raisesEnvironmentErrorwithoutTRINITY_MODEL_PATHpointing at real model weights (tests/tools.py:196-201), and its vLLM engine tests target GPU inference.AI assistance disclosure
OpenAI Codex assisted with repository inspection, the initial implementation, behavioral-test generation, and adversarial review. Anthropic Claude assisted with the native test execution, failure attribution against clean upstream, and drafting this PR description. The submitting human remains responsible for reviewing every changed line and deciding whether to submit the patch. Test evidence above reports actual command outputs, not AI-generated summaries.
Checklist