[Bugfix][FT] Pass stateless process-group timeouts explicitly (#58611)

Signed-off-by: Tyler Michael Smith <tlrmchlsmth@gmail.com> Co-authored-by: Codex <noreply@openai.com>

Tyler Michael Smith committed Sep 28, 2026 at 23:14 UTC 4406874e7c5ffca4b1758f9153e18e57f3020096
6 files changed +39 -14
tests/test_config.py
+15
@@ -5,6 +5,7 @@ import json
5 import logging
6 import os
7 from dataclasses import MISSING, Field, asdict, dataclass, field
8 +from datetime import timedelta
9 from pathlib import Path
10 from types import SimpleNamespace
11 from typing import cast
@@ -1464,6 +1465,20 @@ def test_all2all_backend_has_portable_default():
1465 assert ParallelConfig().all2all_backend == "allgather_reducescatter"
1466
1467
1468 +def test_dp_group_uses_configured_timeout_without_current_config(monkeypatch):
1469 + monkeypatch.setattr(vllm_config_module, "_current_vllm_config", None)
1470 + config = ParallelConfig(cpu_distributed_timeout_seconds=30)
1471 + with (
1472 + patch(
1473 + "vllm.distributed.utils.rendezvous",
1474 + return_value=iter([(torch.distributed.HashStore(), 0, 1)]),
1475 + ),
1476 + patch("vllm.distributed.utils.init_gloo_process_group") as init_group,
1477 + ):
1478 + config.stateless_init_dp_group()
1479 + assert init_group.call_args.kwargs["timeout"] == timedelta(seconds=30)
1480 +
1481 +
1482 @pytest.mark.parametrize(
1483 "dp_size, across_dp, expected",
1484 [(1, False, 4), (1, True, 4), (2, False, 4), (2, True, 8)],
vllm/config/parallel.py
+7
@@ -4,6 +4,7 @@
4 import os
5 import socket
6 from collections.abc import Callable
7 +from datetime import timedelta
8 from typing import TYPE_CHECKING, Any, Literal, overload
9
10 import regex as re
@@ -613,6 +614,11 @@ class ParallelConfig:
614 """
615 return self.data_parallel_external_lb or self.data_parallel_hybrid_lb
616
617 + @property
618 + def cpu_distributed_timeout(self) -> timedelta | None:
619 + seconds = self.cpu_distributed_timeout_seconds
620 + return timedelta(seconds=seconds) if seconds is not None else None
621 +
622 def get_next_dp_init_port(self) -> int:
623 """We might need to initialize process groups in multiple
624 processes that is related to data parallelism,
@@ -694,6 +700,7 @@ class ParallelConfig:
700 backend="gloo",
701 return_store=return_store,
702 listen_socket=listen_socket,
703 + timeout=self.cpu_distributed_timeout,
704 )
705 except DistNetworkError as e:
706 # We only want to retry when the root cause is EADDRINUSE.
vllm/distributed/stateless_coordinator.py
+8
@@ -17,6 +17,8 @@ from vllm.distributed.parallel_state import (
17 )
18 from vllm.distributed.utils import (
19 StatelessProcessGroup,
20 + get_cpu_distributed_timeout_or_none,
21 + get_distributed_timeout_or_none,
22 stateless_destroy_torch_distributed_process_group,
23 stateless_init_torch_distributed_process_group,
24 )
@@ -104,6 +106,10 @@ class StatelessGroupCoordinator(GroupCoordinator):
106
107 backend = str(torch_distributed_backend)
108 self.backend = backend
109 + cpu_timeout = get_cpu_distributed_timeout_or_none()
110 + device_timeout = (
111 + cpu_timeout if backend == "gloo" else get_distributed_timeout_or_none()
112 + )
113 for idx, ranks in enumerate(group_ranks):
114 if self.rank in ranks:
115 self.ranks = ranks
@@ -130,6 +136,7 @@ class StatelessGroupCoordinator(GroupCoordinator):
136 backend=backend,
137 group_name=f"{self.unique_name}_device",
138 listen_socket=socks[0] if socks else None,
139 + timeout=device_timeout,
140 )
141 cpu_group = stateless_init_torch_distributed_process_group(
142 host=host,
@@ -139,6 +146,7 @@ class StatelessGroupCoordinator(GroupCoordinator):
146 backend="gloo",
147 group_name=f"{self.unique_name}_cpu",
148 listen_socket=socks[1] if socks else None,
149 + timeout=cpu_timeout,
150 )
151 tcp_store_group = StatelessProcessGroup.create(
152 host=host,
vllm/distributed/utils.py
+6 -11
@@ -530,8 +530,7 @@ def get_cpu_distributed_timeout_or_none() -> timedelta | None:
530 vllm_config = get_current_vllm_config_or_none()
531 if vllm_config is None:
532 return None
533 - timeout_seconds = vllm_config.parallel_config.cpu_distributed_timeout_seconds
534 - return timedelta(seconds=timeout_seconds) if timeout_seconds is not None else None
533 + return vllm_config.parallel_config.cpu_distributed_timeout
534
535
536 def get_distributed_timeout_or_none() -> timedelta | None:
@@ -582,6 +581,7 @@ def stateless_init_torch_distributed_process_group(
581 group_name: str | None = None,
582 return_store: bool = False,
583 listen_socket: socket.socket | None = None,
584 + timeout: timedelta | None = None,
585 ) -> ProcessGroup | tuple[ProcessGroup, Store]:
586 """A replacement for `torch.distributed.init_process_group` that does not
587 pollute the global state. The created ProcessGroup object can be used for
@@ -617,18 +617,13 @@ def stateless_init_torch_distributed_process_group(
617 is skipped and a ``TCPStore`` server is created directly using the
618 pre-bound socket. This is useful for eliminating TOCTOU races
619 between port allocation and binding.
620 +
621 + If *timeout* is None, use PyTorch's default for the backend.
622 """
623 init_method = get_tcp_uri(host, port)
624 backend = Backend(backend) # it is basically string
623 - timeout = _get_default_timeout(backend)
624 - if backend == "gloo":
625 - gloo_timeout = get_cpu_distributed_timeout_or_none()
626 - if gloo_timeout is not None:
627 - timeout = gloo_timeout
628 - else:
629 - device_timeout = get_distributed_timeout_or_none()
630 - if device_timeout is not None:
631 - timeout = device_timeout
625 + if timeout is None:
626 + timeout = _get_default_timeout(backend)
627
628 if listen_socket is not None:
629 store = create_tcp_store(
vllm/v1/fault_tolerance/engine_core_sentinel.py
+2 -3
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING
9
10 import msgspec
11
12 -from vllm.config import set_current_vllm_config
12 from vllm.distributed import stateless_destroy_torch_distributed_process_group
13 from vllm.distributed.utils import stateless_init_torch_distributed_process_group
14 from vllm.logger import init_logger
@@ -123,8 +122,7 @@ class EngineCoreSentinel:
122 engine = self.engine
123 executor = engine.model_executor
124
126 - with set_current_vllm_config(engine.vllm_config):
127 - ft_request.params.update(self._reinit_dp_group())
125 + ft_request.params.update(self._reinit_dp_group())
126 if hasattr(engine, "step_counter"):
127 engine.step_counter = 0
128
@@ -165,6 +163,7 @@ class EngineCoreSentinel:
163 parallel_config.data_parallel_size,
164 backend="gloo",
165 return_store=True,
166 + timeout=parallel_config.cpu_distributed_timeout,
167 )
168 )
169 return {"new_stateless_dp_group_ports": worker_ports}
vllm/v1/worker/sentinel/gpu_worker_sentinel.py
+1
@@ -65,6 +65,7 @@ class WorkerSentinel:
65 self.dp_rank,
66 self.dp_size,
67 backend="gloo",
68 + timeout=self.worker.parallel_config.cpu_distributed_timeout,
69 )
70
71 def _clean_worker_state(self):