Prhub

#50999 [BugFix] Use file:// rendezvous for single-node executors to eliminate startup port races

原始 PR 作者 aoshen02 合并时间 2026-08-10 10:00 文件变更 6 提交数 11 评论 9 代码增减 +142 / -13

执行摘要

本地 executor rendezvous 改用 file://,消除启动端口竞争

启动期端口竞态是真实可复现的故障:get_open_port() 先绑定探测 socket、关闭后返回端口号,而真正的 TCPStore 绑定发生在 worker 启动之后,间隔内其他本地进程可抢占该端口,报 DistNetworkError ... EADDRINUSE。关联 Issue #41983 记录了 --data-parallel-size > 1 时多个 MultiprocExecutor 并发初始化必然踩中该 TOCTOU 竞态;首个 commit 标注 Fixes: #20310。PR body 的核心判断是“本地 executor 的 rendezvous 根本不需要 TCP 监听”,因此用 UUID 唯一化的 file:// 路径从构造上消除竞态。

值得精读。PR 虽小但完整示范了并发竞态从定位、确定性复现(instrument 抢端口脚本)到修复与行为锁定(多节点覆盖单测)的闭环;FileStore raw path 的 removeprefix 处理、CPU 共享内存标识的 basename 退化都是容易被忽视的细节。建议结合 RFC #51275 一起阅读,理解它在端口竞争系统治理中的位置。

讨论亮点

该 PR 的 review 交锋极少:claude[bot] 因 fork 源自动审查被禁用,维护者 tlrmchlsmth 直接 APPROVED("LGTM!"),review_comments_count 为 0。真正有价值的设计讨论沉淀在 PR body 中——作者主动修正了早期描述中“file:// 也会用于多节点启动”的错误表述,强调 nnodes > 1 时 URI 会被 init_distributed_environment 覆盖为 tcp://master_addr:master_port,并新增单测把这条约束锁定;另外 mergify[bot] 曾提示 pre-commit 失败,作者补齐 ruff/mypy/SPDX 等钩子后经多次 Buildkite CI 验证通过。

实现拆解

  1. 新增 rendezvous 生成函数:vllm/utils/network_utils.py 引入 tempfile,新增 get_file_store_init_method(),返回 file://<tempfile.gettempdir()>/vllm_dist_<uuid4().hex>。路径按 UUID 唯一,不绑定也不释放任何端口资源。
  2. 替换两处端口探针:MultiprocExecutor._init_executor 由 get_distributed_init_method(get_loopback_ip(), get_open_port()) 改为 get_file_store_init_method();UniProcExecutor._distributed_args 同理,并清理不再使用的 get_distributed_init_method、get_ip、get_open_port import。
  3. 核心逻辑改造:vllm/distributed/parallel_state.py 的 init_distributed_environment 在调用 init_process_group 前检测 file:// 前缀,用 removeprefix("file://") 取出原始路径构造 torch.distributed.FileStore(path, world_size),并将 store 显式传入;_init_process_group_for_split_group 新增 store 形参透传;普通分支在 store 非 None 时把 init_method 置为 None。tcp:// 与 env:// 分支零改动,多节点 nnodes > 1 的 TCP 覆盖逻辑保持不变。
  4. CPU 后端适配:vllm/v1/worker/cpu_worker.py 中用于 allreduce 共享内存命名的 VLLM_DIST_IDENT,在 file:// 时取 os.path.basename(removeprefix("file://")),否则保持原 split(":")[-1],规避 POSIX 共享内存名不允许 / 的限制。
  5. 测试配套:新增 tests/distributed/test_file_store.py 三个用例,分别覆盖 raw path 保留(目录名含 #?)、双进程 spawn rendezvous 后 all_reduce 成功、以及多节点下 file:// 必须被覆盖为 tcp://;作者另在双机 H200 上完成原生 MP TP=2 与单机实例的基线/候选对比验证。
文件 模块 状态 重要度
vllm/distributed/parallel_state.py 分布式状态 modified 6.28
tests/distributed/test_file_store.py 文件存储 added 7.21
vllm/v1/executor/multiproc_executor.py 执行器 modified 5.71
vllm/utils/network_utils.py 网络工具 modified 5.53
vllm/v1/worker/cpu_worker.py CPU 后端 modified 5.44
vllm/v1/executor/uniproc_executor.py 执行器 modified 4.83

关键符号

get_file_store_init_method init_distributed_environment _init_process_group_for_split_group MultiprocExecutor._init_executor UniProcExecutor._distributed_args VLLM_DIST_IDENT 派生(cpu_worker.py) _file_store_worker test_file_store_preserves_raw_path test_file_store_rendezvouses_local_processes test_multinode_overrides_file_store_rendezvous

关键源码片段

vllm/distributed/parallel_state.py core-logic

核心改造点:init_distributed_environment 对 file:// 前缀构造 FileStore 并透传 store 参数,_init_process_group_for_split_group 同步扩展;同时保留 nnodes > 1 的 TCP 覆盖逻辑,是行为边界所在。

# init_distributed_environment:对 file:// 前缀的 URI 直接构造 FileStore。
# 用 removeprefix 取原始路径而不是 URL 解析,确保 TMPDIR 含 `#`、`?`
# 等元字符时路径不被截断(test_file_store_preserves_raw_path 验证)。
store = None
if distributed_init_method.startswith("file://"):
    store = torch.distributed.FileStore(
        distributed_init_method.removeprefix("file://"), world_size
    )
​
if envs.VLLM_DISTRIBUTED_USE_SPLIT_GROUP:
    # split_group 分支同样透传 store;device_id 绑定逻辑不受影响。
    _init_process_group_for_split_group(
        backend=backend,
        distributed_init_method=distributed_init_method,
        store=store,
        world_size=world_size,
        rank=rank,
        local_rank=local_rank,
        timeout=timeout,
    )
else:
    # init_method 与 store 互斥:显式给 store 时 init_method 置 None。
    # tcp:// 与 env:// 分支零改动;nnodes > 1 时此处已被覆盖为
    # tcp://master_addr:master_port,因此 file:// 只命中单节点本地。
    torch.distributed.init_process_group(
        backend=backend,
        init_method=distributed_init_method if store is None else None,
        store=store,
        world_size=world_size,
        rank=rank,
        timeout=timeout,
    )
vllm/v1/worker/cpu_worker.py core-logic

CPU 后端适配:VLLM_DIST_IDENT 派生逻辑需兼容 file:// 路径,否则 POSIX 共享内存名含 `/` 会失败。

# VLLM_DIST_IDENT 用于创建 allreduce 共享内存;POSIX 共享内存名
# 不允许出现 `/`,因此 file:// 路径必须退化为 basename(UUID 保证
# 全局唯一),tcp 路径保持原“取端口号”行为,两分支互不影响。
init_method = self.distributed_init_method
os.environ["VLLM_DIST_IDENT"] = (
    os.path.basename(init_method.removeprefix("file://"))
    if init_method.startswith("file://")
    else init_method.split(":")[-1]
)

评论区精华

fork PR 自动审查被禁用,维护者直接批准 other

claude[bot] 回复本 PR 来自 fork,自动审查被禁用,维护者可评论 @claude review 触发一次性审查;实际未触发,tlrmchlsmth 直接 APPROVED。

结论:无实质性技术审查交锋,PR 由维护者人工批准。 · 已解决

多节点 rendezvous 是否受影响的设计澄清 设计

PR body 主动修正早期描述:file:// 仅用于单节点本地 executor;nnodes > 1 时 init_distributed_environment 会把 URI 覆盖为 tcp://master_addr:master_port。新增单测 test_multinode_overrides_file_store_rendezvous 用 monkeypatch 锁定该行为。

结论:明确本 PR 不改变跨节点 TCP rendezvous,行为边界被测试固化。 · 已解决

FileStore raw path 处理(URL 元字符) 设计

实现选择 removeprefix 直接取原始路径构造 FileStore,而非 URL 解析,避免 TMPDIR 含 `#`、`?` 时路径被截断;测试用 store#? 目录专门验证。

结论:raw path 方案被采纳并进入主干。 · 已解决

pre-commit 检查失败与 CI 重跑 style

mergify[bot] 提示 pre-commit 失败,要求安装 pre-commit>=4.5.1 并跑全量钩子;作者随后多次 /ci run 触发 Buildkite(#82830、#82859、#82989、#82997)。

结论:后续提交通过全部钩子与 CI。 · 已解决

风险与影响

  1. 分布式初始化核心路径变更:vllm/distributed/parallel_state.py 是所有 v1/DP/TP 初始化的必经之地,改动仅对 file:// 前缀生效,tcp:// 与 env:// 行为不变;但后续任何向该分支新增逻辑的改动都需回归三种场景。
  2. split_group 分支测试覆盖不足:新增测试全部设置 VLLM_DISTRIBUTED_USE_SPLIT_GROUP=0,_init_process_group_for_split_group 的 store 透传路径没有直接测试,存在接口漂移风险。
  3. FileStore 依赖本地文件系统:TMPDIR 不存在或不可写时会启动失败;非正常退出可能遗留 vllm_dist_* 临时文件。早期 commit 曾有 shutdown 清理逻辑,最终版未见显式清理,属轻微运维残留。
  4. CPU 后端 VLLM_DIST_IDENT 语义变化:file:// 下由端口号变为 UUID basename,若其他代码依赖端口形态会有隐性影响(当前未见)。
  5. 多节点安全边界:依赖 init_distributed_environment 中 nnodes > 1 的覆盖逻辑,该边界已被单测锁定;若覆盖逻辑被移除,file:// 在无共享文件系统的多节点场景会失效。

用户与系统层面:所有使用 v1 引擎、默认 mp 或 uniproc 后端启动的单节点部署(含 data_parallel_size > 1 并发初始化)不再偶发 EADDRINUSE 崩溃;启动日志中的 rendezvous URI 从 tcp:// 变为 file:///tmp/vllm_dist_。平台面:CPU 后端的共享内存标识派生逻辑同步调整。影响范围仅限启动 rendezvous,对模型加载、调度、采样与推理性能均无影响,跨节点 native-MP 行为不变。项目层面:作为 RFC #51275 类别 I(probe-then-bind)在单节点 executor 侧的落地,为后续 ZMQ 前端、DP 世界组等其他探针位点的治理提供了可复制的模式。

分布式初始化核心路径变更 split_group 分支缺少直接测试覆盖 临时文件可能残留 CPU 共享内存标识语义变更

关联 Issue

#41983 [Bugfix] Fix TOCTOU port race in MultiprocExecutor (data_parallel_size > 1)
#51275 [RFC]: Race-free port management: pick ports at bind time, publish over an existing channel

完整报告

参与讨论