Repository navigation
Conversation
…xpert IDs Signed-off-by: ruit <ruit@nvidia.com>
Signed-off-by: ruit <ruit@nvidia.com>
Signed-off-by: ruit <ruit@nvidia.com>
…ing, update docs Signed-off-by: ruit <ruit@nvidia.com>
…replay buffer A zero-variance group dropped on the token-capture path was already marked ready, and abort() ignores ready slots, so the sampler could still pick it after its rows were cleared. Remove the slot first. Also drop an unreachable config check and its test, remove a broken custom-sampler test, and tighten comments and docs. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
… mock - test_single_controller_actor: add pop_step_reward_metrics/filter_zero_variance_groups to the _train_pump_controller mock (the unconditional step-close call regressed 31 tests) - config: use per-algo (grpo/ppo) prefix in dynamic-sampling errors; note the 'custom' sampler and its stamping requirement in the sampler check message - rollout_manager: add docstring to the filter_zero_variance_groups property - test_ppo_setup: cover custom sampler acceptance (supplies the required target) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
…mpler capability - Drop the align_reward_with_v1 reward/filtered_reward swap. On SC, `reward` stays the trained-rows mean (consistent whether or not dynamic sampling is on), and the pre-filter progress signal is carried by the always-reported unfiltered_reward / first_attempt_reward, rather than re-labeling an existing key to match the v1 path. - Gate _take_replacement on the sampler's _sampler_stamps_target_steps capability instead of inferring "no step is waiting on a replacement" from a per-call target_step==None. The sibling guards in _credit_shortfall/_promote_into_step keep their None checks (they use target_step as a key), so only _take_replacement changes. - Update docs and tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
… API Signed-off-by: ruit <ruit@nvidia.com>
…inventory Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
…unter on zero-variance drops Two fixes on the single-controller dynamic-sampling path: - PPO + use_dynamic_sampling slipped through setup validation after use_dynamic_sampling was removed from the SC `unsupported` list: with on_dropped_prompt="replace", a stamping sampler and num_generations_per_prompt>=2 it validated, then failed a divisibility assert deep in the value workers (#2625) -- a dropped group shortens the step and the critic shards it against value.train_global_batch_size, which the separate PPO drop-budget guard does not catch. Reject it explicitly at setup; dynamic sampling is GRPO-only. - The non-capture zero-variance drop returned SKIPPED without resetting _consecutive_infra_drops, unlike a commit and unlike the finalizer path (which resets on a successful generation). A healthy generation that is policy-dropped now clears the consecutive-drop run so it cannot prematurely trip max_consecutive_dropped_prompts while the fleet is answering. Add tests covering both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
|
/ok to test 4f9bbf6 |
4f9bbf6 to
73e2434
Compare
…y_first and weight_fifo samplers Replacement and shortfall accounting no longer depend on a sampler stamping target steps. An optional DropAccountingSampler protocol lets each sampler say whether it uses the spare pool, whether the last step may close short at data exhaustion, and which step a permanently lost group is charged to. The controller tracks the step being assembled and charges unstamped groups there; windowed charges nothing and closes short at the tail. Config validation drops the sampler restriction on dynamic sampling and the hard error for drop budgets under unstamped samplers. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
…sampler weight_fifo fills a step only from groups sharing one start_weight, but a replacement for a filtered group is generated later under a newer version. The oldest version then stays short while the gate is closed and nothing is in flight, so the run stalls (observed: 6 ready groups at version 1, 10 at version 2, trainer at 2, step needs 8). Fail at setup instead. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
73e2434 to
de629ae
Compare
…ingSampler under dynamic sampling Without all three members the controller falls back to the gated defaults, which is wrong for an ungated sampler that keeps producing. isinstance on the protocol is all-or-nothing, so a partial implementation is silently ignored; the warning names which members are missing. It runs in setup, with the other fail-fast checks, so it fires before any cluster resources are allocated. Also correct the protocol docstring and the docs wording for custom samplers. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: ruit <ruit@nvidia.com>
|
/ok to test 4fae850 |
Signed-off-by: ruit <ruit@nvidia.com>
|
/ok to test db5f343 |
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
The optional sampler protocol is a useful separation between gated and continuously producing policies. I inspected all 16 changed files and traced admission, shortfall accounting, training claims, checkpoint restoration, and reward tallies.
Critical (1)
One correctness issue remains: losses arriving during a nonfinal training chunk can over-credit the current step, leaving the following step short without accounting for it.
Suggestions (1)
Quantitative training evidence would help validate the newly supported sampling policies.
Validation was static only: no tests or linters were executed, and no delegated independent review was available.
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.
| """Record that a stamped step will never receive a group it is waiting for.""" | ||
| if target_step is None: | ||
| """Record that a step will never receive a group it is waiting for.""" | ||
| charged_step = self._charge_step_for_lost_group(target_step) |
There was a problem hiding this comment.
1 action item — please fix in this PR.
TL;DR — multiple losses during a nonfinal chunk can shrink its target below the number already selected, discarding accounting needed by the next step.
AI-1 — [BUG] Prevent shortfall credits from crossing selected work
PR-introduced; confidence: 97. In _credit_shortfall, every unstamped loss still goes to the same assembling step until the train pump advances it.
Minimal case: ready-first, four prompts per step, streaming minimum three, lookahead one, shrink mode, two tolerated drops, and batch fraction 0.5:
- Select three groups for step 0. The selection-time check leaves the assembling version at 0 because the target is still four.
- While that chunk trains asynchronously, two lookahead prompts are permanently dropped. Both credits land on step 0, reducing its target to two despite three groups already being selected.
- The loop exits, then prunes both credits. With two admitted batches, only three groups remain, but step 1 still requires four and raises “rollout exhausted before a complete training step was assembled.” Expected: two legal three-group steps, with one loss charged to each.
Action: Coordinate this credit transition with selection progress in the train pump. Track groups already selected, including the currently training chunk; once a loss consumes the final unselected slot, advance unstamped accounting before another loss can be charged there. Also reconcile losses arriving while selection awaits its training claim. The invariant is that a closed step's selected groups plus charged losses equal its configured size—credits must never excuse already-selected work. This spans the selection/credit transition, so I cannot safely offer a one-line replacement.
Preserve: Keep stamped in-order losses on their stamped step, windowed losses uncharged until exhaustion, and custom samplers' explicit charge decisions intact. Retain next-step charging for a loss during an already-final chunk, as asserted by the existing regression.
Acceptance checks (proposed, not executed): Drive the two-batch case above with two drops during the three-group chunk: expect shortfalls {0: 1, 1: 1} before cleanup, two optimizer steps consuming three groups each, and normal exhaustion. Repeat with only one drop: step 0 closes at three and step 1 remains full. Retain the existing final-chunk assertion that the loss remains under step 1, plus the stamped, windowed, and custom charging assertions.
| - A sampler that stamps a target step: `in_order`, or a `custom` sampler that does. The other built-in samplers (`weight_fifo`, `ready_first`, `windowed`) are rejected, because without a stamped step a dropped group can be neither replaced nor counted as missing. | ||
| - `grpo.num_generations_per_prompt >= 2`. | ||
|
|
||
| `in_order`, `ready_first` and `windowed` work; `weight_fifo` is rejected at setup (see below). Each decides what it costs to lose a group for good, which is when no spare is left or the replacement budget is spent: |
There was a problem hiding this comment.
1 action item — suggestion.
AI-1 — [PERF-EVIDENCE] Add quantitative validation for the supported policies
PR-introduced validation gap; confidence: 100. The newly supported training policies change batch boundaries, the number of completed updates, and which generated rewards enter training. The supplied run evidence diagnoses the rejected weight-FIFO stall, but does not measure throughput or convergence for the supported paths; this is not a demonstrated performance regression.
Action: Attach reproducible small-run results comparing dynamic sampling under in-order, ready-first, and windowed: resolved configs/seeds, generated and trained group counts, dropped/replaced counts, groups or tokens per second, and reward curves against generated/trained samples.
Preservation and acceptance: Keep the existing in-order baseline and use comparable model/data and generation budgets; do not compare only equal step counts because windowed can finish fewer updates. The evidence should show that the supported paths complete as configured and make their sampling cost and reward trajectory visible. This is an experimental-validation request, not a proposed code patch; no runs were executed in this review.
a88bc72 to
ede988d
Compare
What does this PR do ?
Extends single-controller dynamic sampling (#4588, which supports only
in_order) to theready_firstandwindowedsamplers, and makesweight_fifofail fast at setup because it cannot work with dynamic sampling.This PR is stacked on #4588 (base branch
ruit/dapo_dynamic_sampling_sc); review that first.Issues
#1365
Background
Dynamic sampling drops prompt groups whose generations all got the same reward, then resamples a replacement so each step keeps its batch size. In #4588 the replacement and shortfall accounting is keyed on the
target_stepa sampler stamps on each slot, and onlyInOrderSamplerstamps one. Every other sampler was rejected by config validation.What this PR supports
async_rl.sampler.namein_orderready_firstwindowedlen(dataloader)steps; the last step closes on the groups left, subject tomin_step_batch_fraction.on_dropped_prompt: replaceis not required.weight_fifocustomDropAccountingSamplerit gets the gated defaults: spare pool on, no short close at exhaustion, a lost group charged to its owntarget_step(or the step being assembled if the sampler stamps none). A stamping sampler therefore behaves likein_order, a non-stamping one likeready_first. An ungated, continuously producing sampler should implement the protocol withuses_replacement_pool = Falseandcloses_short_at_exhaustion = True.Dynamic sampling stays GRPO-only (PPO is rejected, as in #4588).
Result
How it works
DropAccountingSamplerprotocol (@runtime_checkable, so existing custom samplers are unaffected). A sampler declaresuses_replacement_pool,closes_short_at_exhaustion, andcharge_step_for_lost_group(target_step, assembling_version)(which step a permanently lost group is charged to, orNone).BaseSamplersupplies defaults matching the gated, unstamped behaviour;InOrderSampleroverrides them explicitly with its current behaviour;WindowedSampleroverrides them for the no-pool / close-short case._assembling_version) and charges groups from unstamped samplers to it. State (shortfalls, spare pool, replacement counts) stays in the controller; no new checkpointed state, and the checkpoint fieldsampler_stamps_target_stepskeeps its name for compatibility.windowedcloses its last step on whatever groups are left once rollout is exhausted, instead of raising "rollout exhausted before a complete training step".unfiltered_reward,zero_variance_fraction, ...) now also work for unstamped samplers:RolloutManagertakes a step provider (the assembling step) and uses it when a group has notarget_step. For unstamped samplers this is approximate, since a group is tallied under the step being assembled when it finishes.on_dropped_prompt: replaceis still required, except forwindowed.Why
weight_fifois rejectedweight_fifobuilds a step only from groups that share onestart_weight:selectpicks the oldest in-window version and waits for that version alone to fill the step. Dynamic sampling breaks this:Observed on a real run (
weight_fifo,max_staleness_versions=1, 8 prompts/step), at the stall:16 groups were ready, enough for two steps, yet none could be selected because the oldest version had 6 of the 8 needed. The spare pool still had 73 prompts, so this is not budget exhaustion. The same buffer contents are selectable under
ready_first(reproduced against the real sampler classes with a fake buffer).Making
weight_fifowork would mean either letting a step mix weight versions (which is whatready_firstalready is) or stamping replacements with the old version (which mislabels freshly generated data). Neither is worth it, so setup now fails with:Known limit:
weight_fifowithon_dropped_prompt: replaceand drops from infrastructure failures (not dynamic sampling) can hit the same bucket mismatch. That is not changed here.Usage
Testing
tests/unit/single_controller/: sampler hooks (test_sampler_interface.py), spare-pool and shortfall behaviour for unstamped samplers (test_rollout_pump.py,test_single_controller_actor.py), per-step reward metrics (test_step_reward_metrics.py), config validation including theweight_fiforejection (test_ppo_setup.py,test_resiliency_config.py).weight_fifostall above was reproduced on a cluster run of the V2 DAPO recipe; the PR does not add a nightly test.Before your PR is "Ready for review"
Pre checks:
Additional Information
single_controller.py,staleness_sampler.py,config.pyand tests.docs/guides/single-controller.mdnow has the per-sampler table and theweight_fifonote.🤖 Generated with Claude Code