nemo_rl.environments.nemo_gym#
Module Contents#
Classes#
Configuration fields required to select the NeMo Gym rollout path. |
|
Health endpoint and required backend count for one external service. |
|
Startup gate for external services used by NeMo Gym rollouts. |
|
This environment class isn’t really used for training. It’s really meant as an integration wrapper around NeMo-Gym that hooks into the existing NeMo RL resource management via ray. So there is still one source of truth for resource management in NeMo RL. |
|
The live actors behind one NeMo-Gym stack, sharded or not. |
Functions#
Count tokens in Gym-extracted reasoning and answer text. |
|
Fail readably when Gym did not stamp an agent_ref onto every row. |
|
Determine whether NeMo Gym should handle rollouts and validation. |
|
Return whether a postprocessed rollout contains NaN policy logprobs. |
|
Map a NeMo-Gym HTTP failure onto a typed, PICKLABLE failure, or None if not one. |
|
Return the uv cache directory inside a container, or None outside one. |
|
Return the NeMo Gym venv directory from NEMO_GYM_VENV_DIR, or None. |
|
Map the setup-derived generation backend to Gym’s capture backend. |
|
Remove and validate NeMo-RL’s external readiness block from Gym config. |
|
Return a readiness problem for one service, or None when it is ready. |
|
Wait for all external Gym dependencies under one shared deadline. |
|
Flag a NeMo-Gym output item as an invalid tool call / malformed thinking. |
|
Return nemo_gym’s pad_dynamic_image_shapes from an env config, or False. |
|
Return per-component rewards from a NeMo Gym verify result, or None. |
|
Build |
|
Assert each multi-reward result sets |
|
Build the |
|
Turn one already-resolved Gym config mapping into a |
|
Return the entry name Gym uses to route a row. |
|
Expand shards into one (shard, replica_index) entry per actor. |
|
Read the NeMo-Gym entry of |
|
Create and spin up every NeMo-Gym actor this job needs. |
|
The pre-sharding path: one actor, no placement group, no discovery. |
|
Start every shard at once, naming the shard behind any failure. |
|
Ask one replica per shard what it spawned, then build routing metadata. |
|
Spin up a single NeMo-Gym actor against the given generation server URLs. |
|
Fail at setup if any row names a route no shard hosts. |
|
Collect the agent or task-source names a dataset’s rows reference. |
|
Read a stable Gym source once per controller process. |
|
Data#
API#
- nemo_rl.environments.nemo_gym.NEMO_GYM_ACTOR_FQN#
‘nemo_rl.environments.nemo_gym.NemoGym’
- nemo_rl.environments.nemo_gym.NEMO_GYM_GRACEFUL_SHUTDOWN_TIMEOUT_S#
120
- nemo_rl.environments.nemo_gym.GYM_SERVER_TYPE_KEYS#
(‘responses_api_agents’, ‘responses_api_models’, ‘resources_servers’)
- nemo_rl.environments.nemo_gym.DEFAULT_SHARD_NAME#
‘nemo_gym’
- nemo_rl.environments.nemo_gym.DEFAULT_SHARD_CPUS#
8
- nemo_rl.environments.nemo_gym.DEFAULT_SHARD_PG_READY_TIMEOUT_SECONDS#
180.0
- nemo_rl.environments.nemo_gym.DEFAULT_SHARD_SPINUP_TIMEOUT_SECONDS#
1800.0
- nemo_rl.environments.nemo_gym.DEFAULT_SHARD_DRAIN_TIMEOUT_SECONDS#
None
- nemo_rl.environments.nemo_gym._ROUTED_EXPERTS_DTYPES#
None
- nemo_rl.environments.nemo_gym.DEFAULT_INVALID_TOOL_CALL_PATTERNS#
[‘<tool_call>’, ‘</tool_call>’, ‘<function_call>’, ‘</function_call>’]
- nemo_rl.environments.nemo_gym.DEFAULT_THINKING_TAGS#
[’
’, ‘ ’]
- nemo_rl.environments.nemo_gym._count_structured_response_tokens(
- tokenizer: transformers.PreTrainedTokenizerBase,
- *,
- reasoning_text: str,
- answer_text: str,
Count tokens in Gym-extracted reasoning and answer text.
- nemo_rl.environments.nemo_gym._require_resolved_agent_refs(nemo_gym_examples: list[dict]) None#
Fail readably when Gym did not stamp an agent_ref onto every row.
run_examplesresolvestask_sourcetoagent_refin place before it returns, and every read after that point – this module’s counters, and Gym’s own dispatch, which posts torow["agent_ref"]["name"]– assumes it happened. Unguarded, a row that was not resolved surfaces asKeyError: 'agent_ref'inside a Ray TaskError inside an ExceptionGroup, forty lines from anything that names the cause.The cause worth naming is a version skew rather than a bad row.
task_sourcerouting is new: an older Gym has no resolver, so a dataset prepared with a current Gym – which stripsagent_refand stampstask_sourceinstead – arrives unroutable. That happens when the Gym actor’s venv is older than the checkout that prepared the data, which is whatNRL_FORCE_REBUILD_VENVS=trueexists to correct.
- class nemo_rl.environments.nemo_gym.NemoGymCompatibleConfig#
Bases:
typing.ProtocolConfiguration fields required to select the NeMo Gym rollout path.
- property env: dict[str, Any]#
- property policy: nemo_rl.models.policy.PolicyConfig#
- class nemo_rl.environments.nemo_gym.ExternalServiceReadinessTargetConfig#
Bases:
pydantic.BaseModelHealth endpoint and required backend count for one external service.
urlmust be avllm_pool_lb/healthendpoint; see_probe_external_servicefor the reply it expects. A plain vLLM/healthreturns no backend counts and never reads as ready.- name: str#
‘Field(…)’
- url: str#
None
- expected_backends: int#
‘Field(…)’
- classmethod validate_url(url: str) str#
Require an absolute HTTP(S) health endpoint.
- class nemo_rl.environments.nemo_gym.ExternalServiceReadinessConfig#
Bases:
pydantic.BaseModelStartup gate for external services used by NeMo Gym rollouts.
- services: list[nemo_rl.environments.nemo_gym.ExternalServiceReadinessTargetConfig]#
‘Field(…)’
- timeout_seconds: float#
‘Field(…)’
- poll_interval_seconds: float#
‘Field(…)’
- request_timeout_seconds: float#
‘Field(…)’
- validate_unique_service_names() Self#
Reject ambiguous duplicate service labels in diagnostics.
- nemo_rl.environments.nemo_gym.should_use_nemo_gym(
- master_config: nemo_rl.environments.nemo_gym.NemoGymCompatibleConfig,
Determine whether NeMo Gym should handle rollouts and validation.
- nemo_rl.environments.nemo_gym._has_nan_generation_logprobs(result: dict) bool#
Return whether a postprocessed rollout contains NaN policy logprobs.
- nemo_rl.environments.nemo_gym._typed_gym_failure(error: Exception) Optional[Exception]#
Map a NeMo-Gym HTTP failure onto a typed, PICKLABLE failure, or None if not one.
Classification has to happen here, on the raising side, because
run_rolloutsruns inside theNemoGymRay actor and the exception must survive the actor boundary to reach the retry policy on the driver.It does not survive. aiohttp’s
raise_for_statuspassesheaders=self.headers, and those are aCIMultiDictProxy, which cloudpickle cannot serialize – so Ray drops the cause and the driver receives a bareRayTaskErrorwith no type and no.status. Every gym HTTP failure then classified DATA, capping the gym path atmax_data_attempts_per_prompt(2) and leavingmax_attempts_per_prompt(5) unreachable on the very path whose dead-endpoint scenario motivates it. Two things made that the dominant case rather than a corner: Gym’s middleware turns inner-server failures into 500 – exactly the status the INFRA branch is for – and its transport layer retries disconnects in an uncapped loop, so those never arrive at all.GymTransportErrorandRolloutDataFailuretake a single str, so they pickle cleanly andclassify_rollout_failure’s explicit-class fast path wins on the far side.Returns None when the exception carries no HTTP status, leaving the caller to re-raise it untouched.
- nemo_rl.environments.nemo_gym.get_nemo_gym_uv_cache_dir() str | None#
Return the uv cache directory inside a container, or None outside one.
Inside a container (NRL_CONTAINER=1), returns the uv cache location so Gym stores its caches in the expected shared path. Returns None outside a container, meaning the caller should omit this arg and let Gym create the cache locally (the default when you may not be able to write to /opt).
- nemo_rl.environments.nemo_gym.get_nemo_gym_venv_dir() str | None#
Return the NeMo Gym venv directory from NEMO_GYM_VENV_DIR, or None.
Returns the value of NEMO_GYM_VENV_DIR if set, otherwise None. When None the caller should omit this arg and let Gym create venvs locally (the default when a container is not used since you may not be able to write to /opt).
- class nemo_rl.environments.nemo_gym.NemoGymConfig#
Bases:
typing.TypedDict- model_name: str#
None
- base_urls: List[str]#
None
- policy_api_key: NotRequired[Optional[str]]#
None
- initial_global_config_dict: Dict[str, Any]#
None
- port_range_low: NotRequired[int]#
None
- port_range_high: NotRequired[int]#
None
- invalid_tool_call_patterns: NotRequired[List[str] | None]#
None
- thinking_tags: NotRequired[List[str] | None]#
None
- require_routed_experts: NotRequired[bool]#
None
- routed_experts_dtype: NotRequired[str]#
None
- use_fastokens: NotRequired[bool]#
None
- external_service_readiness: NotRequired[nemo_rl.environments.nemo_gym.ExternalServiceReadinessConfig | None]#
None
- tokenizer_config: NotRequired[Optional[nemo_rl.models.policy.TokenizerConfig]]#
None
- pad_dynamic_image_shapes: NotRequired[bool]#
None
- token_capture: NotRequired[Dict[str, Any] | None]#
None
- nemo_rl.environments.nemo_gym._POLICY_SERVER_NAME#
‘policy_model’
- nemo_rl.environments.nemo_gym._NG_ROLLOUT_ID_BODY_KEY#
‘_ng_rollout_id’
- nemo_rl.environments.nemo_gym._TOKEN_CAPTURE_CONTROL_PREFIX#
‘/training-token-capture/control’
- nemo_rl.environments.nemo_gym._TOKEN_CAPTURE_CONTROL_ENV#
‘NEMO_GYM_TOKEN_CAPTURE_CONTROL_TOKEN’
- nemo_rl.environments.nemo_gym._POLICY_API_KEY_ENV#
‘NEMO_RL_POLICY_API_KEY’
- nemo_rl.environments.nemo_gym._external_staging_backend(
- token_capture: Dict[str, Any],
Map the setup-derived generation backend to Gym’s capture backend.
- nemo_rl.environments.nemo_gym.extract_external_service_readiness(
- nemo_gym_dict: dict[str, Any],
Remove and validate NeMo-RL’s external readiness block from Gym config.
- nemo_rl.environments.nemo_gym._probe_external_service(
- service: nemo_rl.environments.nemo_gym.ExternalServiceReadinessTargetConfig,
- *,
- request_timeout_seconds: float,
Return a readiness problem for one service, or None when it is ready.
The URL must be a
vllm_pool_lb/healthendpoint. This reads thestatus,healthy_backendsandtotal_backendsfields returned byLoadBalancer.handle_healthintools/external_gym_vllm/vllm_pool_lb.py.
- nemo_rl.environments.nemo_gym._wait_for_external_services(
- config: nemo_rl.environments.nemo_gym.ExternalServiceReadinessConfig | None,
Wait for all external Gym dependencies under one shared deadline.
- nemo_rl.environments.nemo_gym._detect_invalid_tool_call_and_malformed_thinking(
- output_item_dict: dict[str, Any],
- invalid_tool_call_patterns: list[str] | None = None,
- thinking_tags: list[str] | None = None,
Flag a NeMo-Gym output item as an invalid tool call / malformed thinking.
Inspects the final output item of a model turn. For a final content message, any thinking tag is malformed (thinking should never leak into the answer); for a reasoning summary, only a repeated tag (count > 1) is malformed (a single pair is expected). A textual tool-call pattern in either indicates an invalid (unexecuted) tool call.
- Returns:
(is_invalid_tool_call, has_malformed_thinking).
- nemo_rl.environments.nemo_gym.get_pad_dynamic_image_shapes(
- env_config: collections.abc.Mapping[str, Any],
Return nemo_gym’s pad_dynamic_image_shapes from an env config, or False.
Takes
master_config.envrather than the whole config: interpreting NeMo-Gym settings belongs with the environment, and callers outside it only need the resolved boolean.The NemoGym actor reads the same key from its own config for the per-turn attach. The initial-payload attach runs in the driver instead, so it has to be read here and passed down, or multi-image prompts would be processed under different rules on the two paths.
- class nemo_rl.environments.nemo_gym.NemoGym(cfg: nemo_rl.environments.nemo_gym.NemoGymConfig)#
Bases:
nemo_rl.environments.interfaces.EnvironmentInterfaceThis environment class isn’t really used for training. It’s really meant as an integration wrapper around NeMo-Gym that hooks into the existing NeMo RL resource management via ray. So there is still one source of truth for resource management in NeMo RL.
Initialization
- _require_spinup() None#
Raise a diagnosable error if this instance never ran :meth:
_spinup.
- async health_check() None#
Raise if the Gym head server or any subprocess server has died.
Thin wrapper over NeMo-Gym’s own
RunHelper.poll, which is whatgym env startcalls every 60s fromrun_forever. NeMo-RL only callsrh.start, so without this the check Gym already implements never runs and a dead tool server surfaces as unexplained rollout timeouts instead of a named process.Run the synchronous poll in a worker thread so this probe does not block concurrent rollouts on the actor’s event loop.
- _spinup() None#
Start the NeMo-Gym head server and rollout collection helper.
Deferred from init so the actor can be created cheaply (and scheduled onto reserved nodes) and spun up explicitly once the vLLM server URLs are available, overlapping with vLLM model loading.
- set_tokenizer(tokenizer: transformers.PreTrainedTokenizerBase) None#
Install the tokenizer run_rollouts postprocesses with.
Called once per actor at spinup. It used to be a run_rollouts argument, which meant Ray deserialized a tokenizer per prompt on this actor’s task-execution thread. That thread holds the GIL, so it blocked the actor’s event loop and no rollout could issue its first HTTP request until its own copy finished loading.
The cost is not marginal. A tokenizer of this shape measured 7.45 MB on the wire, 286 ms to serialize and 1052 ms to deserialize, so a SingleController recipe admitting ~1000 prompts per step spends roughly 18 minutes deserializing a single admission burst, against a step that should take minutes. Runs on that shape stalled without completing a rollout.
Measured on one CPU node with 1024 concurrent calls against one actor: 153 of 1024 prompts finished in 300 s passing the tokenizer per call, versus all 1024 in 16 s holding it here. Passing an ObjectRef instead does not help – Ray caches the object buffer, not the deserialized value, so it still pays per task.
- _control_client()#
Gym ServerClient resolving servers by name from the head server.
- async _control(method: str, path: str, **kwargs: Any) dict#
- list_entries() Dict[str, List[str]]#
Report which config entries this actor actually spawned.
Returns
{entry_name: [server_type_keys]}read from Gym’s resolved config, so entries that arrived viaconfig_pathsare included. The config NeMo RL passed in is not a substitute: it still holdsconfig_pathsas file paths and none of the entries they expand into, so reading it would miss every agent and judge loaded from a path.Entries whose server config has no
entrypointare omitted because Gym does not start a process for them.Callers compare these names across actors to build the agent->shard map and to catch an entry duplicated across shards; an unsharded job’s coverage check (
validate_dataset_agent_coverage) reads them as the entries a dataset row may name. Names are all that is interpreted; what an entry means is Gym’s business.
- async run_rollouts(
- nemo_gym_examples: list[dict],
- timer_prefix: str,
- deduplicate_multimodal_data: bool = False,
- per_prompt: bool = False,
Stream postprocessed rollouts as NeMo-Gym tasks complete.
A thin span-opening wrapper over :meth:
_stream_rollouts, which holds the logic. Split so the span covers the whole stream without indenting the body under awith.The decorator parents this batch to the caller’s span, and everything below inherits it – including the HTTP requests Gym’s own client makes, which are instrumented at the library level (see
- Func:
instrument_aiohttp_client) and read the ambient context rather than anything passed here.
An umbrella span either way, so it carries no
rl.bucket: several of these are in flight at once, and their durations would sum past the wall clock they happened in.- Parameters:
per_prompt – Whether the caller dispatches this once per prompt. Decided by the caller because
in_per_prompt_scopereads aContextVarin the calling process, and this is a separate Ray actor. On the single-controller path a step issues one of these per prompt, so the span belongs inper_promptrather thanper_step, whose count is meant to scale with steps.- Yields:
One
(rowidx, resolved_agent_ref, result, timing_metrics)tuple per completed task, in completion order rather than input order.rowidxechoes back the_rowidxthe caller stamped on the example, which is how the caller maps a result to its slot.timing_metricsisNoneon every tuple but the last, which carries the batch totals.
- async _stream_rollouts(
- nemo_gym_examples: list[dict],
- timer_prefix: str,
- deduplicate_multimodal_data: bool = False,
Body of :meth:
run_rollouts; see there for the tracing wrapper.
- async _postprocess_receipt_mode(
- nemo_gym_row: dict,
- nemo_gym_result: dict,
Fetch the ledger manifest and assemble the receipt locally.
The legacy token walk (and its contiguity assert) does not run: the capture ledger owns lineage, output items carry no token arrays, and the canonical row is rebuilt by the finalizer from staged deltas. The Ray return carries only the receipt (~100 B/call) beside the agent-level result.
- static _assemble_receipt(
- rollout_id: str,
- manifest: dict,
- *,
- terminal_response_id: Optional[str],
- scored_response: Optional[dict] = None,
- reward: float,
Build the token-free RolloutReceipt payload from a ledger manifest.
terminal_response_idis the served response envelope id the harness reports for the completion it kept (Gym’sterminal_response_idresult key), forwarded toresolve_terminalasdeclared_response_id. It is not the logical-request header.Terminal selection is staged, fail-closed at every stage:
Witness attribution (
resolve_terminal): the declared response id (terminal_response_id), the scored response’s envelope id, and its content fingerprints each independently name a manifest row throughCallRecord.response_idand the recorded fingerprints. Agreeing witnesses attribute; a declared id that matches no row masks and never falls back; disagreeing witnesses attribute nothing.Heuristic fallback (
select_terminal_call): with no witness, the manifest’s explicit parent links infer the terminal (fail-closed: ambiguity masks).
The receipt records the resolving stage in
terminal_selection(declared/response_id/content/heuristic— failed selections stamp the last stage attempted;Nonewhen no stage ran because a manifest row failed to parse) and the witness trail interminal_attribution_reason. A row that failsCallRecordvalidation masks the rollout (invalid_manifest_row) and is dropped from the shipped manifest, so the finalizer can still enumerate and clean the rows that did parse. Retry duplicates are dead-branch rows: they stay in the manifest (their staged rows are fetched, verified, and cleaned) but never join the terminal chain —verify_and_linearizetolerates rows unreferenced by the terminal chain.Poisoning is fail-closed with one carve-out. A failure row whose reason is
request_finished_without_staged_coordinatesis a call that never returned a completion (the ledger commit precedes the response leaving the server) and can never be a lineage parent (an uncommitted call has no row to resolve against) — e.g. the doomed final call of a rollout that exhausted the model’s context window. Such rows are structurally off-chain and do not poison; if the terminal request itself died this way, the missing-terminal-row check below still masks the rollout. Every other failure reason (for exampleworker_capture_failed,invalid_worker_commit_coordinates, orunresolved_parent; a reason-less failure row poisons ascapture_failed) marks a call whose completion WAS served — a hole in the chain — and poisons.
- _postprocess_nemo_gym_to_nemo_rl_result(
- nemo_gym_row: dict,
- nemo_gym_result: dict,
- tokenizer: transformers.PreTrainedTokenizerBase,
- *,
- include_initial_multimodal_data: bool = True,
- shutdown() None#
Stop the Gym servers. Safe to call more than once, and before spinup.
Teardown runs in a finally block and may be requested more than once. RunHelper.shutdown() is not idempotent, so the handle is cleared before it is used. A failure therefore cannot leave a live handle that a later cleanup attempt invokes again.
- abstractmethod step(message_log_batch, metadata)#
- abstractmethod global_post_process_and_metrics(batch)#
- nemo_rl.environments.nemo_gym.extract_reward_components(
- nemo_gym_result: dict,
Return per-component rewards from a NeMo Gym verify result, or None.
Single-reward NeMo Gym environments return only a scalar
reward. Multi-reward environments additionally returnreward_components: a mapping of component-name -> score. These are surfaced asreward/<name>batch keys and consumed by GDPO (seenemo_rl.algorithms.advantage_estimator.GDPOAdvantageEstimator).Returns
Nonewhen the environment is single-reward (noreward_components), so callers fall back to the scalarrewardpath unchanged.
- nemo_rl.environments.nemo_gym.build_reward_component_columns(
- component_dicts: List[Dict[str, float] | None],
Build
reward/<name>batch columns from per-sample reward-component dicts.Takes the union of component names across the batch in sorted (deterministic) order and, for each, emits a
reward/<name>tensor with one entry per sample. A component absent on a given sample is filled with0.0so every column covers all samples (the per-prompt baseline requires each component present for all responses).Keys are prefixed
reward/so they are exactly whatnemo_rl.algorithms.utils.get_gdpo_reward_component_keysselects (it matchesstartswith("reward/")and sorts by name); the name carries the component identity, so no positional index is needed. Returns an empty dict when no sample has components.
- nemo_rl.environments.nemo_gym.validate_reward_components_match_scalar(
- nemo_gym_results: List[dict],
Assert each multi-reward result sets
reward == sum(reward_components).A multi-reward verifier must set the scalar
rewardto the sum of itsreward_componentsso single-reward (GRPO) consumers and GDPO read the same aggregate. We keep the verifier’s scalarrewardastotal_rewardrather than silently overwriting it with the component sum, so a verifier that violates this contract must be surfaced here instead of masked.Raises
ValueErroron the first violating result. A no-op for single-reward results (those withoutreward_components).
- nemo_rl.environments.nemo_gym.setup_nemo_gym_config(config, tokenizer) None#
- nemo_rl.environments.nemo_gym.build_nemo_gym_config(
- env_configs: dict[str, Any],
- *,
- base_urls: list[str],
- model_name: str,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]] = None,
- policy_api_key: Optional[str] = None,
Build the
NemoGymConfigfor a single, unsharded NeMo-Gym actor.Splits
env_configs["nemo_gym"]into the NeMo-RL-side fields the actor reads directly and the remainder, which is forwarded verbatim as NeMo-Gym’s initial global config.- Parameters:
env_configs – The master_config.env mapping; env_configs[“nemo_gym”] supplies the Gym global config plus NeMo-RL detection knobs (invalid_tool_call_patterns, thinking_tags, num_gpu_nodes).
base_urls – Per-DP-rank OpenAI-compatible server base URLs from the generation backend.
model_name – Served model name the Gym rollouts should target.
enable_router_replay – Sets
require_routed_expertsand selects the routed-experts carry dtype (“int8”/”int16”/”int32”) for the model.use_fastokens – Forwarded from
policy.tokenizer.use_fastokensso the actor patches its tokenizer the same way the driver does.policy_api_key – The bearer token the generation server requires on its OpenAI routes (VllmGeneration.http_server_api_key), or None when it checks none.
- Returns:
A
NemoGymConfigwith NeMo-RL fields at the top level and the remainingenv_configs["nemo_gym"]keys underinitial_global_config_dict. The caller’senv_configsis not mutated.- Raises:
ShardConfigError – The config is sharded. One config cannot describe several shards; use :func:
build_nemo_gym_actors.
- nemo_rl.environments.nemo_gym._build_gym_actor_config(
- nemo_gym_dict: dict[str, Any],
- *,
- base_urls: list[str],
- model_name: str,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]] = None,
- policy_api_key: Optional[str] = None,
Turn one already-resolved Gym config mapping into a
NemoGymConfig.Shared by the unsharded path and by each shard, so every actor gets the same treatment of NeMo-RL-side keys regardless of how it was composed.
- nemo_rl.environments.nemo_gym.get_nemo_gym_route_name(
- row: collections.abc.Mapping[str, Any],
Return the entry name Gym uses to route a row.
- class nemo_rl.environments.nemo_gym.NemoGymShardSet#
The live actors behind one NeMo-Gym stack, sharded or not.
An unsharded job is the one-shard, one-replica case, so callers do not need a separate code path for it.
.. attribute:: handles
Shard name to its replica handles, in replica order.
.. attribute:: route_to_shard
Agent or task-source entry name to the shard hosting it. Empty when unsharded, where every row goes to the only actor.
.. attribute:: placement_group
The STRICT_SPREAD group pinning shards to distinct nodes, or None when unsharded.
- handles: Dict[str, List[ray.actor.ActorHandle]]#
None
- route_to_shard: Dict[str, str]#
‘field(…)’
- placement_group: Optional[ray.util.placement_group.PlacementGroup]#
None
- _next_replica: Dict[str, int]#
‘field(…)’
- _replica_lock: threading.Lock#
‘field(…)’
- __getstate__() dict[str, Any]#
- __setstate__(state: dict[str, Any]) None#
- property is_sharded: bool#
- property all_handles: List[Any]#
- property hosted_routes: frozenset[str]#
Agent and task-source entry names this set can route to.
- shard_for_route(route_name: str) str#
Name the shard hosting an agent or task source.
Unsharded jobs have one actor and no map, so every route resolves to it; nothing was discovered because nothing could have conflicted.
- Raises:
ShardSetupError – No shard hosts the route, so its rows have nowhere to go.
- pick_handle(route_name: str) Any#
Choose the actor instance to serve a route’s next prompt group.
The shard is fixed by the data; the replica rotates round-robin. Round robin is deterministic and easy to reason about, which matters more than adaptivity here: within a synchronous step there is no completion feedback to adapt on, so an even split is the best available policy. A least-in-flight policy would require callers to release a lease when each dispatch completes. That lifecycle is intentionally outside this round-robin implementation.
- instance_label(handle: Any) str#
Name one actor, for error messages and metric keys.
A shard with one replica is named by the shard alone, so the common case reads as it did before replicas existed; a replicated shard adds the replica index. This is the same rule the per-shard log directories follow, so a metric and its logs carry the same name.
- sole_handle() Any#
The only actor, for callers that predate routing.
- Raises:
ShardSetupError – There is more than one actor, so picking one would silently drop the rest.
- shutdown(
- *,
- timeout: float | None = NEMO_GYM_GRACEFUL_SHUTDOWN_TIMEOUT_S,
- force_kill: bool = True,
Stop every actor, then release the bundles they were pinned to.
- nemo_rl.environments.nemo_gym._shard_instances( ) List[tuple[nemo_rl.environments.nemo_gym_shards.ShardSpec, int]]#
Expand shards into one (shard, replica_index) entry per actor.
- nemo_rl.environments.nemo_gym.as_nemo_gym_shard_set(
- environment: Any,
Read the NeMo-Gym entry of
task_to_envas a shard set either way.Call sites that predate sharding put a bare actor handle there. Rather than make every one of them build a set first, treat a lone handle as the one-shard, one-replica case it already is, so the routing path is identical whether or not the job is sharded.
- nemo_rl.environments.nemo_gym.build_nemo_gym_actors(
- env_configs: dict[str, Any],
- *,
- base_urls: list[str],
- model_name: str,
- tokenizer: transformers.PreTrainedTokenizerBase,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]] = None,
- policy_api_key: Optional[str] = None,
- pg_ready_timeout: float = DEFAULT_SHARD_PG_READY_TIMEOUT_SECONDS,
- spinup_timeout: float = DEFAULT_SHARD_SPINUP_TIMEOUT_SECONDS,
Create and spin up every NeMo-Gym actor this job needs.
Without
shardsthis makes exactly one actor, scheduled as before. Withshardsit makes one actor per replica, each pinned by a STRICT_SPREAD placement group to a distinct node, each holding its own complete Gym stack. Actors are spun up concurrently, so the wall-clock cost is roughly the slowest shard rather than the sum.- Parameters:
tokenizer – Installed on every actor once it is up, rather than passed per rollout call. See
NemoGym.set_tokenizerfor why.- Returns:
A :class:
NemoGymShardSetwhose actors are all running and validated.- Raises:
ShardSetupError – The bundles could not be placed, a shard failed to start, or the shards’ entries did not pass the startup checks. Any actors already created are torn down first.
- nemo_rl.environments.nemo_gym._build_single_gym_actor(
- nemo_gym_dict: dict[str, Any],
- *,
- base_urls: list[str],
- model_name: str,
- tokenizer: transformers.PreTrainedTokenizerBase,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]],
- policy_api_key: Optional[str] = None,
The pre-sharding path: one actor, no placement group, no discovery.
Discovery is skipped rather than merely unused. Its checks compare entry names between shards, so with one shard there is nothing they could find.
- nemo_rl.environments.nemo_gym._build_sharded_gym_actors(
- nemo_gym_dict: dict[str, Any],
- plan: nemo_rl.environments.nemo_gym_shards.ShardPlan,
- *,
- base_urls: list[str],
- model_name: str,
- tokenizer: transformers.PreTrainedTokenizerBase,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]],
- policy_api_key: Optional[str] = None,
- pg_ready_timeout: float,
- spinup_timeout: float,
- nemo_rl.environments.nemo_gym._spinup_shards_concurrently(
- shard_set: nemo_rl.environments.nemo_gym.NemoGymShardSet,
- spinup_timeout: float,
- *,
- tokenizer: transformers.PreTrainedTokenizerBase,
Start every shard at once, naming the shard behind any failure.
Config faults surface inside
_spinup– a server ref pointing at an entry that is missing from this shard’s slice raises Gym’sServerRefNotFoundErrorthere. Gym names the entry and field but has no concept of a shard, so the shard name is added here.The tokenizer install is a second pass rather than a call queued behind each
_spinup. Queuing both up front would mean waiting on the install to learn the spinup failed, which loses the message above.
- nemo_rl.environments.nemo_gym._discover_route_shard_map(
- shard_set: nemo_rl.environments.nemo_gym.NemoGymShardSet,
- plan: nemo_rl.environments.nemo_gym_shards.ShardPlan,
Ask one replica per shard what it spawned, then build routing metadata.
Replicas of a shard are stamped from one merge, so they host identical entries and only the first needs to be asked.
- nemo_rl.environments.nemo_gym.spinup_nemo_gym_actor(
- env_configs: dict[str, Any],
- *,
- base_urls: list[str],
- model_name: str,
- tokenizer: transformers.PreTrainedTokenizerBase,
- enable_router_replay: bool,
- use_fastokens: bool,
- token_capture: Optional[dict[str, Any]] = None,
- policy_api_key: Optional[str] = None,
Spin up a single NeMo-Gym actor against the given generation server URLs.
When
env_configs["nemo_gym"]["num_gpu_nodes"] > 0, the actor is scheduled with soft NodeAffinity to the caller’s Ray node so its colocated GPU resources land where the caller expects.- Parameters:
tokenizer – Installed on the actor once, here, rather than passed per rollout call. See
NemoGym.set_tokenizerfor why that distinction is the difference between a working run and a stalled one.token_capture – Dumped
TokenCaptureConfigwhen ledger-authoritative token capture is enabled, elseNone. Forwarded tobuild_nemo_gym_config.policy_api_key – The bearer token the generation server requires on its OpenAI routes (VllmGeneration.http_server_api_key), or None when it checks none.
- Returns:
The spun-up
NemoGymRay actor handle (_spinupalready awaited).- Raises:
ShardConfigError – The config is sharded. Callers that dispatch to a single handle cannot serve several shards; they need the router.
- nemo_rl.environments.nemo_gym.validate_dataset_agent_coverage(
- shard_set: nemo_rl.environments.nemo_gym.NemoGymShardSet,
- datasets: collections.abc.Mapping[str, Any],
Fail at setup if any row names a route no shard hosts.
Rows can name a legacy
agent_refor a current Gymtask_source. Without this scan, a rare route can sit unseen for hours of training before its first dispatch fails.A sharded job checks against the route map built at setup. An unsharded job has one actor and no route map, so it asks that actor which entries its resolved Gym config spawned and keeps the routable ones, agents and resources servers, exactly as the sharded route map does. A model or environment server is never a route, on one actor or on many.
- Parameters:
shard_set – The running actors. A sharded set carries the route map built at setup; an unsharded set is asked for its entries here.
datasets – Split name to dataset, for the error message.
Nonevalues and datasets without gym rows are skipped.
- Raises:
ShardSetupError – A split references routes no shard hosts.
- nemo_rl.environments.nemo_gym._iter_dataset_agent_names(dataset: Any) set[str]#
Collect the agent or task-source names a dataset’s rows reference.
A stable source file is scanned once and its names cached; a custom or changed source falls back to scanning the loaded rows.
- nemo_rl.environments.nemo_gym._load_agent_names_from_source( ) frozenset[str] | None#
Read a stable Gym source once per controller process.
- nemo_rl.environments.nemo_gym._get_agent_name(row: object) str | None#