Repository navigation
fix(instruction_following): split counting_composition on * * * and stop counting punctuation as words - #4624
Conversation
… stop counting punctuation as words The count:counting_composition checker from verifiable-instructions asks for paragraphs delimited by `* * *` but splits on `***`, and it counts the period that ends each sentence as a word. A response that follows the instruction always fails this constraint, so it gives no signal on the 2,131 rows of Nemotron-RL-instruction_following that carry it. Replace that one checker with a subclass that keeps the upstream instruction text and arguments and only overrides check_following: paragraphs are split on a `* * *` line, empty segments before the first or after the last divider are ignored, and tokens that are only punctuation are not counted as words. This matches the fix for the same checker in allenai/open-instruct#1718. Every other instruction still uses the upstream checker. Fixes NVIDIA-NeMo#4613 Signed-off-by: Yashvardhan Thanvi <yashvt9404@gmail.com>
…ion-checker Signed-off-by: Yashvardhan Thanvi <yashvt9404@gmail.com>
|
/ok to test 5ba0c55 |
|
/ok to test 70485f9 |
|
/review |
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
HOLD — the required model smoke validation is missing (RISK).
RISK — Validate the changed reward behavior with real model responses
Failure and scope: The English verify() path now grades count:counting_composition with a different checker. The PR explicitly reports no model rollout; its validation uses constructed responses, including the new positive fixtures. Trusted-base AGENTS.md requires representative real model smoke rollouts and inspection for behavior-changing environment work. Constructed-response replay is useful but does not satisfy that requirement. This is a validation gap, not a demonstrated checker failure; it concerns English tasks carrying this constraint.
Correction: Run a small smoke using the existing instruction-following configuration on affected published rows with varied n_sent/n_words, including a row with another constraint. Inspect the generated answers and record the row arguments, checker decisions, and rewards in the PR validation evidence. No checker rewrite is requested unless that inspection exposes a mismatch. The invariant is that real model outputs receive scores consistent with the advertised constraints.
Acceptance and preservation — proposed, not executed: Manually check each sampled answer’s divider, sentence, and tokenizer-based word counts against its follow_instruction_list entry; confirm binary reward equals float(all(bits)) and fractional reward equals sum(bits) / len(bits). Retain the existing positive cases’ reward 1.0/[True], negative cases’ reward 0.0/[False], and the registry-isolation assertions. In particular, spaced dividers with punctuation, CRLF, and outer dividers must keep passing, while contiguous *** and short sentences must keep failing.
All four changed files and the directly affected routing and reward paths were inspected. This was a read-only static review; test and replay results remain author-reported.
Note: formal approval is not available for this request, so this review is posted as a comment only.
Review generated by codex. This was a full review. Request a full review with /review reset=true.
|
tested a rollout, lgtm thanks! |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/40054917034 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/40059189099 |
|
🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/40062258383 |
…ctuation as repeated words (NVIDIA-NeMo#5068) * fix(instruction_following): stop count:count_unique from counting punctuation as repeated words The upstream count:count_unique checker compares the raw nltk tokens with their set, so a punctuation mark that appears twice counts as a repeated word and almost every compliant multi-sentence response fails. Replace it with a local subclass that drops punctuation-only tokens before comparing, the same rule as allenai/open-instruct#1718 and as the local count:counting_composition checker from NVIDIA-NeMo#4624 (NVIDIA-NeMo#4613). Every other instruction still uses the upstream checker. Closes NVIDIA-NeMo#5067 Related to NVIDIA-NeMo#215 Signed-off-by: Tsvetan (Sven) Rankov <tsvetan.rankov@gmail.com> * test(instruction_following): pin more count:count_unique token classes Add four verify() cases for the local checker: typographic quotes, a markdown bullet list, a dropped `$` next to distinct digits, and the inherited contraction split (`n't` repeats, so `Don't stop. Can't wait.` still fails). Also assert that the local class returns the upstream instruction text and arguments, so the published prompts still describe what is graded. Signed-off-by: Tsvetan (Sven) Rankov <tsvetan.rankov@gmail.com> --------- Signed-off-by: Tsvetan (Sven) Rankov <tsvetan.rankov@gmail.com>
What does this PR do?
Fixes #4613.
count:counting_compositioncannot be passed by a response that follows its instruction. The instruction asks to "Write 3 paragraphs, delimited by the markdown divider: * * *, with exactly {n_sent} sentences each, with exactly {n_words} words in each sentence", but the checker in verifiable-instructions (f46a5ac, the head of its default branch, which the unpinnedrequirements.txtinstalls):re.split(r"\s?\*\*\*\s?", value)(L2440), so a* * *divider is never found and the response counts as one paragraph;nltk.word_tokenize(sentence)(L2457), andsplit_into_sentenceskeeps the period that ends each sentence, soCats sleep.is three words.What it does accept is
***dividers with sentences one word short, because the period fills the last word slot. This affects all 2,131 of the 46,391 rows innvidia/Nemotron-RL-instruction_followingthat carry this constraint. The checker is a copy of open-instruct's IFEvalG one, which had the same bug and was fixed in allenai/open-instruct#1718.Changes:
counting_composition.py(new):CountingCompositionCheckersubclasses the upstream class and overrides onlycheck_following. The instruction text, default arguments andget_instruction_args()are inherited, so the published prompts still describe what is graded. The rules match the open-instruct fix:* * *(spaces or tabs around the stars and CRLF line endings are accepted; asterisks inside a paragraph are not a divider);nltk.word_tokenizetoken with at least one letter or digit, so punctuation is no longer counted.app.py: a module-levelINSTRUCTION_DICTis the upstream registry with this one entry replaced, andverify()looks instruction classes up there. Every other instruction still uses the upstream checker.README.md: one paragraph saying this instruction uses a local checker and why.Why a local override: verifiable-instructions has had no commits since 2025-08-20 and its only open PR (from January) has had no maintainer response, so there is no fixed commit to pin. Changing the instruction text to
***would not help the published dataset, whose 2,131 prompts all say* * *. Contiguous***stays rejected, since the instruction names* * *and***is the divider thatparagraphs:paragraphsasks for. The issue suggests\s*\*\s*\*\s*\*\s*; that also accepts***and an inline* * *, so this follows open-instruct's line-anchored rule instead (allenai/open-instruct#1966 is the same report against IFEvalG, closed by #1718). Keeping open-instruct's rules also keeps scores comparable between the two.Unchanged on purpose: contractions still count as two words (
Don'ttokenizes toDoandn't), as in open-instruct.Not in this PR:
count:count_uniquehas the same punctuation problem. It compares theword_tokenizetokens with their set, so any response with two sentences that both end in.repeats the.token and fails. open-instruct fixed it in the same PR; it can follow separately.Validation
Ubuntu (WSL), Python 3.13.14, CPU only. Server venv built from
resources_servers/instruction_following/requirements.txt(verifiable-instructions resolved tof46a5ac), reused through.venvwith+skip_venv_if_present=true:New tests in
tests/test_app.py. The counting_composition cases go throughverify():test_counting_composition_positive(5 cases): the issue's example;,!?punctuation; 3 sentences of 3 words with blank lines around the divider; CRLF with padded dividers; dividers before the first and after the last paragraph.test_counting_composition_negative(9 cases):***with the right counts;***with one-word sentences (main gives reward 1); one-word sentences; three-word sentences; three sentences; two and four paragraphs; an inline* * *; an empty paragraph between dividers.test_only_counting_composition_checker_is_replaced: same keys as the upstream registry, and only this entry differs.With the new tests and main's
app.py: 7 failed, 23 passed (the 5 positive cases get reward 0, the***one-word case gets reward 1, and the registry test cannot importINSTRUCTION_DICT). With this change: 30 passed. The three examples from the issue now returnTrue, False, False(main:False, False, True).Rollouts
No model rollout was run. The change only affects how one checker scores a given text; the agent, prompts, data and other checkers are unchanged. Instead, every one of the 2,131 rows with this constraint was replayed through
verify()(published row format,grading_mode="fraction") with three constructed responses built from that row's ownn_sentandn_words. Counts are rows where thecount:counting_compositionentry offollow_instruction_listis true:* * *dividers, exact counts (follows the instruction)***dividers, exact counts***dividers, one word fewer per sentenceHappy to add a smoke rollout on some of these rows if you want model evidence.
Compatibility and benchmark impact
count:counting_compositionchange: 2,131 of 46,391 (about 4.6%) innvidia/Nemotron-RL-instruction_following. On those rows a compliant response can now pass this constraint, and the***plus short sentences shape that passed before now fails, so rewards and metrics for them are not comparable with earlier runs. The reward profiling numbers in the README were measured with the old checker and were not re-run.INSTRUCTION_DICTis a new module-level name inapp.py.benchmarks/ifevaluses this server, but the IFEval prompts carry nocount:instructions, so its scores do not change.Related PRs
app.pyandtests/test_app.py; merging this branch adds no new conflict hunks there. Its loose check reuses the instruction instance built for the strict check, so after both land loose mode uses the fixed checker too. Its fallback callscheck_following(text, mode="loose")and catchesTypeError; the override keeps upstream's one-argument signature, so it takes the same perturbation path as every other checker.app.py, so whichever merges second keeps both import lines. No other overlap.Proposed labels:
bug,area:environment,community-request.Checklist
pre-commit run --all-files) (so CI lint/format checks pass).git commit -s) (so the DCO check passes).