Skip to content

Commit 9ff9bf7

Browse files
committed
fix(ray): drop Slurm/PMIx/MPI launcher env in Ray workers so TRT-LLM can import
`import tensorrt_llm` runs MPI_Init. Under srun (ray.sub starts every step with --mpi=pmix) Open MPI aborts on an inherited SLURM_STEP_ID ("OMPI was not built with SLURM's PMI support"), and residual OMPI_/MPI_/SLURM_ vars send TRT-LLM to MPI_Comm_Spawn (MPI_ERR_SPAWN). This breaks every multi-node TRT-LLM run on such a cluster, including main's own grpo-qwen3-1.7b-2n4g-automodel-trtllm. A runtime_env cannot delete a variable and TRT-LLM's GPU workers inherit the raylet's environment, so remove the launcher state inside each worker with a job-level worker_process_setup_hook. The driver and ray.sub keep their environment; the OMPI/PRTE root-allow vars set by the TRT-LLM worker are kept. Verified: grpo-qwen3.5-35ba3b-4n4g-megatron-trtllm-mxfp8-noncolocated passes all checks on 4 nodes with an unmodified ray.sub. Signed-off-by: Erin Ho <14718778+hchings@users.noreply.github.com>
1 parent e3da39a commit 9ff9bf7

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

‎nemo_rl/distributed/virtual_cluster.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,36 @@ def init_ray(log_dir: Optional[str] = None) -> None:
436436
safe_set_span_attributes(span, {RAY_CLUSTER_SOURCE_ATTR: outcome})
437437

438438

439+
# Config set by the TRT-LLM worker for Open MPI / PRTE, not launcher state.
440+
_MPI_ROOT_ALLOW_VARS = frozenset(
441+
{
442+
"OMPI_ALLOW_RUN_AS_ROOT",
443+
"OMPI_ALLOW_RUN_AS_ROOT_CONFIRM",
444+
"PRTE_ALLOW_RUN_AS_ROOT",
445+
"PRTE_ALLOW_RUN_AS_ROOT_CONFIRM",
446+
}
447+
)
448+
449+
450+
def _drop_mpi_launcher_env() -> None:
451+
"""Ray ``worker_process_setup_hook``: strip Slurm/PMIx/MPI launcher state.
452+
453+
``import tensorrt_llm`` runs ``MPI_Init``. Under ``srun`` (``ray.sub`` starts
454+
every step with ``--mpi=pmix``) the inherited ``SLURM_STEP_ID``/``PMIX_*``
455+
make Open MPI believe it was direct-launched and abort with "OMPI was not
456+
built with SLURM's PMI support"; residual ``OMPI_*``/``MPI_*``/``SLURM_*``
457+
also push TRT-LLM to ``MPI_Comm_Spawn`` (``MPI_ERR_SPAWN``). Neither an empty
458+
value nor an ``env_vars`` override helps, and TRT-LLM's GPU workers are Ray
459+
actors whose environment is the raylet's plus a copy, so the variables must
460+
be removed inside each worker process before it imports anything.
461+
"""
462+
for key in list(os.environ):
463+
if key in _MPI_ROOT_ALLOW_VARS:
464+
continue
465+
if key.startswith(("PMIX_", "PMI_", "MPI_", "OMPI_", "SLURM_")):
466+
os.environ.pop(key, None)
467+
468+
439469
def _init_ray(log_dir: Optional[str] = None) -> str:
440470
"""Initialise Ray.
441471
@@ -466,6 +496,7 @@ def _init_ray(log_dir: Optional[str] = None) -> str:
466496

467497
runtime_env = {
468498
"env_vars": env_vars, # Pass thru all user environment variables
499+
"worker_process_setup_hook": _drop_mpi_launcher_env,
469500
}
470501

471502
cvd = os.environ.get("CUDA_VISIBLE_DEVICES", "ALL")

‎tests/unit/distributed/test_virtual_cluster.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,3 +991,34 @@ def test_actor_registry_honors_system_flag(use_system_executable):
991991
"nemo_rl.models.policy.workers.automodel_policy_worker.AutomodelPolicyWorker"
992992
].startswith("uv run")
993993
assert envs["nemo_rl.environments.nemo_gym.NemoGym"].startswith("uv run")
994+
995+
996+
def test_init_ray_drops_mpi_launcher_env_in_every_worker():
997+
"""TRT-LLM's MPI init aborts under srun's launcher env, so workers must not carry it."""
998+
from nemo_rl.distributed.virtual_cluster import init_ray
999+
1000+
with (
1001+
patch("ray.init") as mock_ray_init,
1002+
patch("ray.cluster_resources") as mock_cluster_resources,
1003+
):
1004+
mock_cluster_resources.return_value = {"nrl_tag_0": 1}
1005+
with patch.dict(os.environ, {"CUDA_VISIBLE_DEVICES": "0"}, clear=True):
1006+
init_ray()
1007+
1008+
hook = mock_ray_init.call_args_list[0][1]["runtime_env"][
1009+
"worker_process_setup_hook"
1010+
]
1011+
launcher = {
1012+
"SLURM_STEP_ID": "14",
1013+
"SLURM_JOB_ID": "7",
1014+
"PMIX_NAMESPACE": "x",
1015+
"OMPI_MCA_plm": "slurm",
1016+
"MPI_LOCALRANKID": "0",
1017+
}
1018+
root_allow = {"OMPI_ALLOW_RUN_AS_ROOT": "1", "PRTE_ALLOW_RUN_AS_ROOT": "1"}
1019+
unrelated = {"HOME": "/root"}
1020+
with patch.dict(os.environ, {**launcher, **root_allow, **unrelated}, clear=True):
1021+
hook()
1022+
assert not set(launcher) & set(os.environ)
1023+
assert all(os.environ[k] == v for k, v in {**root_allow, **unrelated}.items())
1024+
hook() # a worker that never had any is fine

0 commit comments

Comments
 (0)