You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cleanup sync engine in inference backend, since we'll drop sync rollout path, so sync engine will no longer be used.
Known missing items
✅ Resolved
Epoch semantics in the Single Controller train loop: _train_pump was bounded only by max_train_steps, with no max_num_epochs/current_epoch notion of full dataset passes. — ✅ Fixed in feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 (feat(sc): epoch-bounded rollout passes (max_num_epochs parity), commit 4c2bf5b6c): SC now has max_num_epochs + a _current_epoch counter with epoch-bounded rollout dispatch, surfaced in run()/ping().
Add a retry policy to the Single Controller train loop._train_pump's per-cycle error path (single_controller.py L437-L573) aborts the worker step and re-raises on any mid-cycle failure (microbatch OOM, NaN-guard trip, prepare_logprobs / begin / finish failure), so a transient error kills the run — there is no retry. Flagged by the TODO(sc): retry policy is a follow-up at L441. (Flagged during feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 review.) — Not planned.
Support custom samplers by extracting the TQReplayBuffer implementation. — ✅ Done in feat(sc): train path — StalenessSampler + _train_pump rewrite #3220: PromptGroupSampler protocol + WindowedSampler / WeightFifoSampler / InOrderSampler built-ins in nemo_rl/algorithms/async_utils/staleness_sampler.py, plus CustomSamplerConfig(target="module:ClassName") wired through create_sampler and the AsyncRLConfig.sampler discriminated union.
Honor recompute_kv_cache_after_weight_updates in async mode. — ✅ Done in feat(sc): setup + entrypoint #3266: SC's _sync_weights (nemo_rl/algorithms/single_controller.py:615) calls self._gen.invalidate_kv_cache() after every weight sync when AsyncRLConfig.recompute_kv_cache_after_weight_updates=True.
Decouple the SC logprob refresh from the advantage config. — ✅ Done in feat(sc): setup + entrypoint #3266: SC's _policy_logprobs_required / _reference_logprobs_required predicates (single_controller.py:104-110) are loss-driven (force_on_policy_ratio, seq_logprob_error_threshold, skip_reference_policy_logprobs_calculation), replacing the old advantage-config gating.
Support multi-reward (GDPO) in AsyncRolloutImpl. Multi-reward aggregation — needed for the GDPO recipe — was dropped when the async rollout impl was introduced in feat: add AsyncRolloutManager for native async per-prompt rollouts #2566; wire it back through the async path for GDPO parity with grpo.
Generation-level backpressure in the Single Controller — cap in-flight generations, not prompt groups. Today _rollout_pump backpressures per prompt group (max_inflight_prompts + the _buffer_capacity semaphore), so a group with one slow generation holds its buffer slot for the whole group → long-tail under-utilization (can't admit the next group even with spare generation capacity). Doing it properly needs the rollout/generation-worker rewrite that streams each generation into the DataPlane as it completes — today generate_and_push pushes a whole group atomically, so SC never sees individual generations finish and a generation-level semaphore has nothing to release against mid-group. Track with that rewrite; until then max_inflight_prompts stays as a conservative bound (under-utilizes on long tails but never over-subscribes DataPlane memory). (Discussed in feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700, thread feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 (comment), Known Missing Feature in feat(sc): rollout path — TQReplayBuffer, rollout_manager, _rollout_pump #3219)
Add an over_sampling_ratio (>1) knob to bound rollout over-sampling. The rollout pipeline can over-sample beyond what the trainer will consume; a configurable ratio lets recipes cap wasted generations while keeping enough headroom for slow tails. (Known Missing Feature in feat(sc): rollout path — TQReplayBuffer, rollout_manager, _rollout_pump #3219)
Support multiple mini-steps inside a single RL step. The SC train loop today does exactly one optimizer.step per RL step; PPO-style multi-mini-step updates (iterating minibatches over the same batch of rollouts) need to be plumbed through the train pump. (Known Missing Feature in feat(sc): train path — StalenessSampler + _train_pump rewrite #3220)
Add a drain gate to refit. Refit today does not gate in-flight rollouts to fully drain before swapping weights, so straggler generations can still be produced against the pre-refit weights. Needed for correctness on tight staleness bounds. (Known Missing Feature in feat(sc): setup + entrypoint #3266)
Support (or clearly reject) non-colocated Megatron generation in the Single Controller.setup.py asserts backend != "megatron" on the non-colocated cluster split, whereas legacy grpo.py supports it via a dedicated inference policy (init_megatron_generation non-colocated branch builds MegatronGeneration(cluster=inference_cluster, ...)). All SC recipes use vLLM generation so this is latent; either mirror grpo's non-colocated Megatron-generation path, or keep the fail-loud guard with a message that names the generation backend rather than reading like it's about Megatron training. (Flagged during feat(sc): setup + entrypoint #3266 review)
FP8 KV-cache scale sync not implemented in the Single Controller. grpo's train loop, when the vLLM backend reports requires_kv_scale_sync (FP8 KV cache), computes FP8 QKV scales via policy.calibrate_qkv_fp8_scales(...) and forwards them through sync_weights(..., kv_scales=...) (feature). SC's _sync_weights calls sync_weights() with no kv_scales and never calls calibrate_qkv_fp8_scales, so an FP8-KV-cache rollout under SC keeps stale quantization scales after weight updates (degraded generation, cf. fix: forward calibrated KV cache scales on colocated ZMQ IPC refit path #3226). The synchronizer interfaces already accept kv_scales; only the driver-side compute+forward is missing. (Flagged during feat(sc): setup + entrypoint #3266 review)
SC NeMo-Gym non-vLLM guard is unreachable dead code, placed after the expensive build. The if generation_config["backend"] != "vllm": raise NotImplementedError in setup_single_controller (setup.py:380) can't fire through the real entrypoint: sglang+gym trips _should_use_nemo_gym's should_expose_http_serverassert and megatron+gym trips _build_generation's ValueError first; the guard only sits after _build_generation/_build_trainer (multi-minute cluster+model build). test_nemo_gym_rejects_non_vllm_backend reaches it only by mocking both earlier gates. Hoist the check to right after use_nemo_gym = _should_use_nemo_gym(...) (setup.py:327) so megatron+gym fails fast with a clear message before any build. (Flagged during feat(sc): NeMo-Gym path #3267 review.)
Tighten the Single Controller NeMo-Gym functional-test signal once SC validation is wired.tests/functional/grpo_async_gym_single_controller.sh currently checks only max(data["train/reward"]) > 0, which is a liveness bound; replace it with validation accuracy or a trending mean-reward threshold comparable to grpo_async_gym.sh after validation metrics are available. (Flagged during feat(sc): NeMo-Gym path #3267 review.)
Overlap Single Controller NeMo-Gym spinup with deferred vLLM model loading. SC currently completes _build_generation/finish_generation before spinup_nemo_gym_actor, serializing setup; mirror grpo.py by reserving server URLs with defer_model_load=True and running Gym spinup alongside vLLM weight loading (and policy initialization for non-colocated execution). Tracked by the TODO in single_controller_utils/setup.py. (Flagged during feat(sc): NeMo-Gym path #3267 review.)
Above: Jul 28 version.
val_start_at to delay periodic validation. Skip periodic validation until a configured training step, so early cheap steps don't pay for eval before the model is worth measuring. (Landed in grpo feat(grpo): add val_start_at to delay periodic validation #3400.)
Tracking issue for loose ends in the Single Controller / Async RL / Streaming.
Items will be checked off or split into separate issues as they land.
Known cleanup items
Drop after new implementation aligned with old implementation.
nemo_rl/algorithms/async_utils/trajectory_collector.py.nemo_rl/algorithms/async_utils/replay_buffer.py.nemo_rl/experience/rollouts.py, new implementation is in refactor: unify per-prompt rollout to RolloutManager #2567 and we'll no longer maintain sync rollout path.Known missing items
✅ Resolved
Epoch semantics in the Single Controller train loop:— ✅ Fixed in feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 (_train_pumpwas bounded only bymax_train_steps, with nomax_num_epochs/current_epochnotion of full dataset passes.feat(sc): epoch-bounded rollout passes (max_num_epochs parity), commit4c2bf5b6c): SC now hasmax_num_epochs+ a_current_epochcounter with epoch-bounded rollout dispatch, surfaced inrun()/ping().Add a retry policy to the Single Controller train loop.— Not planned._train_pump's per-cycle error path (single_controller.py L437-L573) aborts the worker step and re-raises on any mid-cycle failure (microbatch OOM, NaN-guard trip, prepare_logprobs / begin / finish failure), so a transient error kills the run — there is no retry. Flagged by theTODO(sc): retry policy is a follow-upat L441. (Flagged during feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 review.)Support custom samplers by extracting the TQReplayBuffer implementation.— ✅ Done in feat(sc): train path — StalenessSampler + _train_pump rewrite #3220:PromptGroupSamplerprotocol +WindowedSampler/WeightFifoSampler/InOrderSamplerbuilt-ins innemo_rl/algorithms/async_utils/staleness_sampler.py, plusCustomSamplerConfig(target="module:ClassName")wired throughcreate_samplerand theAsyncRLConfig.samplerdiscriminated union.Honor— ✅ Done in feat(sc): setup + entrypoint #3266: SC'srecompute_kv_cache_after_weight_updatesin async mode._sync_weights(nemo_rl/algorithms/single_controller.py:615) callsself._gen.invalidate_kv_cache()after every weight sync whenAsyncRLConfig.recompute_kv_cache_after_weight_updates=True.Decouple the SC logprob refresh from the advantage config.— ✅ Done in feat(sc): setup + entrypoint #3266: SC's_policy_logprobs_required/_reference_logprobs_requiredpredicates (single_controller.py:104-110) are loss-driven (force_on_policy_ratio,seq_logprob_error_threshold,skip_reference_policy_logprobs_calculation), replacing the old advantage-config gating.Router replay (R3) not supported/validated on the Single Controller (async + TransferQueue) path.— ✅ Done in feat(sc): support router replay with TQ #3378: routed-expert indices are threaded through the async rollout →TQReplayBuffer→ training payload, with fail-loud validation whenrouter_replay.enabled=trueand routes are missing; adds SC + R3 recipegrpo-qwen3-30ba3b-10n8g-megatron-cp2-r3-async-single-controller.{yaml,sh}+ a nightly correctness test. Closes [Single Controller] Router replay (R3) not supported/validated on the async + TransferQueue path #3327.Support multi-reward (GDPO) in
AsyncRolloutImpl. Multi-reward aggregation — needed for the GDPO recipe — was dropped when the async rollout impl was introduced in feat: add AsyncRolloutManager for native async per-prompt rollouts #2566; wire it back through the async path for GDPO parity with grpo.Support the
greedyoption inRolloutManager. Dropped during the streaming rewrite in refactor: unify per-prompt rollout to RolloutManager #2567; needed for greedy validation/eval sampling before the sync path can be fully retired.Fix env observation chat templating at the env interface level. Templating currently leaks across the env boundary and each backend rolls its own; move it into the env interface so observations arrive already templated. Flagged in feat: add AsyncRolloutManager for native async per-prompt rollouts #2566, context feat: Add Tau bench environment #2479.
Generation-level backpressure in the Single Controller — cap in-flight generations, not prompt groups. Today
_rollout_pumpbackpressures per prompt group (max_inflight_prompts+ the_buffer_capacitysemaphore), so a group with one slow generation holds its buffer slot for the whole group → long-tail under-utilization (can't admit the next group even with spare generation capacity). Doing it properly needs the rollout/generation-worker rewrite that streams each generation into the DataPlane as it completes — todaygenerate_and_pushpushes a whole group atomically, so SC never sees individual generations finish and a generation-level semaphore has nothing to release against mid-group. Track with that rewrite; until thenmax_inflight_promptsstays as a conservative bound (under-utilizes on long tails but never over-subscribes DataPlane memory). (Discussed in feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700, thread feat(algorithms): SingleController streaming train_pump (split-API consumer) #2700 (comment), Known Missing Feature in feat(sc): rollout path — TQReplayBuffer, rollout_manager, _rollout_pump #3219)Add an
over_sampling_ratio(>1) knob to bound rollout over-sampling. The rollout pipeline can over-sample beyond what the trainer will consume; a configurable ratio lets recipes cap wasted generations while keeping enough headroom for slow tails. (Known Missing Feature in feat(sc): rollout path — TQReplayBuffer, rollout_manager, _rollout_pump #3219)Support multiple mini-steps inside a single RL step. The SC train loop today does exactly one
optimizer.stepper RL step; PPO-style multi-mini-step updates (iterating minibatches over the same batch of rollouts) need to be plumbed through the train pump. (Known Missing Feature in feat(sc): train path — StalenessSampler + _train_pump rewrite #3220)Add a drain gate to refit. Refit today does not gate in-flight rollouts to fully drain before swapping weights, so straggler generations can still be produced against the pre-refit weights. Needed for correctness on tight staleness bounds. (Known Missing Feature in feat(sc): setup + entrypoint #3266)
Support (or clearly reject) non-colocated Megatron generation in the Single Controller.
setup.pyassertsbackend != "megatron"on the non-colocated cluster split, whereas legacygrpo.pysupports it via a dedicated inference policy (init_megatron_generationnon-colocated branch buildsMegatronGeneration(cluster=inference_cluster, ...)). All SC recipes use vLLM generation so this is latent; either mirror grpo's non-colocated Megatron-generation path, or keep the fail-loud guard with a message that names the generation backend rather than reading like it's about Megatron training. (Flagged during feat(sc): setup + entrypoint #3266 review)FP8 KV-cache scale sync not implemented in the Single Controller. grpo's train loop, when the vLLM backend reports
requires_kv_scale_sync(FP8 KV cache), computes FP8 QKV scales viapolicy.calibrate_qkv_fp8_scales(...)and forwards them throughsync_weights(..., kv_scales=...)(feature). SC's_sync_weightscallssync_weights()with nokv_scalesand never callscalibrate_qkv_fp8_scales, so an FP8-KV-cache rollout under SC keeps stale quantization scales after weight updates (degraded generation, cf. fix: forward calibrated KV cache scales on colocated ZMQ IPC refit path #3226). The synchronizer interfaces already acceptkv_scales; only the driver-side compute+forward is missing. (Flagged during feat(sc): setup + entrypoint #3266 review)SC NeMo-Gym non-vLLM guard is unreachable dead code, placed after the expensive build. The
if generation_config["backend"] != "vllm": raise NotImplementedErrorinsetup_single_controller(setup.py:380) can't fire through the real entrypoint: sglang+gym trips_should_use_nemo_gym'sshould_expose_http_serverassert and megatron+gym trips_build_generation'sValueErrorfirst; the guard only sits after_build_generation/_build_trainer(multi-minute cluster+model build).test_nemo_gym_rejects_non_vllm_backendreaches it only by mocking both earlier gates. Hoist the check to right afteruse_nemo_gym = _should_use_nemo_gym(...)(setup.py:327) so megatron+gym fails fast with a clear message before any build. (Flagged during feat(sc): NeMo-Gym path #3267 review.)Tighten the Single Controller NeMo-Gym functional-test signal once SC validation is wired.
tests/functional/grpo_async_gym_single_controller.shcurrently checks onlymax(data["train/reward"]) > 0, which is a liveness bound; replace it with validation accuracy or a trending mean-reward threshold comparable togrpo_async_gym.shafter validation metrics are available. (Flagged during feat(sc): NeMo-Gym path #3267 review.)Overlap Single Controller NeMo-Gym spinup with deferred vLLM model loading. SC currently completes
_build_generation/finish_generationbeforespinup_nemo_gym_actor, serializing setup; mirrorgrpo.pyby reserving server URLs withdefer_model_load=Trueand running Gym spinup alongside vLLM weight loading (and policy initialization for non-colocated execution). Tracked by the TODO insingle_controller_utils/setup.py. (Flagged during feat(sc): NeMo-Gym path #3267 review.)Above: Jul 28 version.
val_start_atto delay periodic validation. Skip periodic validation until a configured training step, so early cheap steps don't pay for eval before the model is worth measuring. (Landed in grpo feat(grpo): add val_start_at to delay periodic validation #3400.)Validation-only sampling params. Let validation use its own
temperature/top_p(typically near-greedy) so accuracy is a stable metric independent of training's exploration setting. (Landed in grpo feat(grpo): validation-only sampling params and grouped pass@k validation #3401.)Optional env-flagged sample masking. Gate NeMo-Gym env-flagged sample dropping (default on) behind
grpo.mask_env_flagged_samples; today it's always on and makes effective batch composition non-deterministic, which hurts controlled experiments / A/B runs. (Landed in grpo feat(grpo): make env-flagged sample masking optional #3402.)Add anything else here as it comes up.