Repository navigation
Conversation
Contributor
|
🌿 Preview your docs: https://nvidia-preview-pr-4643-a9a1c904f865.docs.buildwithfern.com/nemo/gym |
The rollout collection driver runs every rollout's dispatch and result handling on one asyncio event loop, so each Python garbage collection pauses all of them at once. With Python's default thresholds of (700, 10, 10), a long-context run with many rollouts in flight collects often, and the longest pauses show up directly in the driver's gym.event_loop.lag_ms. Add two RolloutCollectionConfig fields that gym eval run applies to its own process before collection starts, with and without --no-serve. driver_gc_freeze, on by default, runs one full collection and calls gc.freeze(), so later collections skip the objects loaded at startup. driver_gc_thresholds, (50000, 50, 100) by default, is passed to gc.set_threshold(). Setting driver_gc_freeze=false and driver_gc_thresholds=null keeps Python's defaults. A threshold below 1 is rejected, because a zero turns off automatic collection. Only the gym eval run entry points apply the settings. Resources, agent, and model servers run in their own processes, and library callers of run_examples, such as NeMo RL, keep their own garbage-collection state. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub
force-pushed
the
ananthsub/eval-driver-gc-tuning
branch
from
October 16, 2026 01:30
005dbab to
a9a1c90
Compare
This was referenced Oct 16, 2026
This branch was previously deployed
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.
What changed and why
The rollout collection driver that
gym eval runstarts runs every rollout's dispatch and result handling on one asyncio event loop, so each Python garbage collection pauses all of them at once. Python's default thresholds are(700, 10, 10). In the CPU capacity simulator, a long-context run with rollout observability on makes the driver onmainrun about 1,460 collections, with about 2 seconds of total pause and single pauses of about 400 ms. Most of that pause time comes from a handful of full (generation 2) collections, each of which scans every object the driver holds.RolloutCollectionConfiggains two Hydra-configurable fields, and bothgym eval runentry points (with and without--no-serve) apply them to their own process once, before collection starts:driver_gc_freeze(defaulttrue) runs one full collection and callsgc.freeze(), so later collections no longer scan the modules, config, and clients loaded at startup.driver_gc_thresholds(default[50000, 50, 100]) is passed togc.set_threshold(). A value below 1 is rejected by config validation, because a zero turns off automatic collection.Setting
++driver_gc_freeze=false ++driver_gc_thresholds=nullkeeps Python's defaults. The settings are applied by_tune_driver_gcinnemo_gym/cli/eval.py, never byRolloutCollectionHelper, so library callers ofrun_examplessuch as NeMo RL keep their own garbage-collection state. Resources, agent, and model servers run in their own processes and are unaffected. Without--no-serve, the head server runs inside the driver's process and so shares its settings.The defaults come from a local experiment that applied the same freeze and thresholds through environment variables. A history pass over
nemo_gym/cli/eval.pyandRolloutCollectionConfigfound no earlier garbage-collection tuning or reason against it, and the onlygcuse innemo_gymis the telemetry observer that reportsgym.gc.*metrics.The option is documented next to the other rollout collection settings in
fern/versions/latest/pages/reference/cli-commands.mdx.Because the no-serve entry point now changes process-wide state, an existing test that runs it through
main()would leave a frozen heap and raised thresholds in its pytest worker. A new autouse fixture intests/conftest.pyunfreezes and restores the thresholds after every test.Issue
No issue: this is a focused performance change driven by the capacity simulator measurements below.
Compatibility
gym eval runnow changes the driver's garbage-collection settings by default. Rollout outputs and metrics are unchanged. Memory held by reference cycles is freed less often, and the two fields above restore the previous behavior.Validation
PYTHONPATH=. .venv/bin/python -m pytest -q tests/unit_tests/test_cli_eval.pycovers the no-serve and serving entry points applying the default thresholds and freeze before collection starts, configured thresholds being applied, the disabled settings leaving garbage collection untouched, and invalid thresholds being rejected. The entry-point tests fail whennemo_gym/cli/eval.pyis reverted tomain.PYTHONPATH=. .venv/bin/python -m pytest -n 16 -q tests/unit_tests --ignore=tests/unit_tests/test_exporters.pyin a venv with thedevandtelemetryextras. The only failures are intest_opensandbox_compose.py,test_reward_profile.py, andtest_sandbox_stop_retry.py, which depend on the local environment and fail the same way onmain.pre-commit runon the changed files.Rollout evidence
N/A for model rollouts: the change only adjusts the driver process's garbage-collection settings and does not change any agent, verifier, or model server behavior.
Capacity simulator comparison against
mainat98ffb644f9, which already includes #4644 (release finished rollout tasks) and #4646 (aggregate collected rollouts off the event loop). The production topology ran 4,096 rollouts per run (64 repeats, 1,024 in flight, 80 history messages of 2,000 characters, observability and model-call capture on), withgym eval run --no-serveas the driver.tuned_offis this branch with++driver_gc_freeze=false ++driver_gc_thresholds=null. Every run completed all rollouts with none failed. Values come from the driver's last console metric export.gym.gc.collections_totalgym.event_loop.lag_msmeangym.event_loop.lag_msmaxmain1main2tuned_off1tuned_off2The defaults cut driver collections by about 24 times and total pause time by about 4 times. With the settings turned off, the branch matches
main.By generation,
mainruns about six full collections that account for most of its pause time, each taking 120 to 385 ms. This branch runs no full collection. It runs 59 young (generation 0) collections of at most 11 ms each and one generation 1 collection of 222 to 245 ms. That one pause remains because survivors of 50 young collections accumulate in generation 1 before it is collected, and in one run it landed while rollouts were still in flight. A lower second threshold would split it into several shorter pauses, which can be tuned separately withdriver_gc_thresholds.The longest event-loop lag stays above 800 ms in every run, well above any garbage-collection pause on this branch, so other work on the event loop dominates the worst lag. The shared machine was busy during these runs, and lag varied widely between repeats of the same variant.
Running the earlier local experiment, which applied the same freeze and thresholds through environment variables, under the same settings gives the same result as this branch: 60 collections, 0.47 s of total pause, and a longest pause of 222 ms.