Prhub

#51097 [Bugfix] Preserve non-logitproc entry points in tests

原始 PR 作者 d4l3k 合并时间 2026-08-11 06:14 文件变更 2 提交数 1 评论 4 代码增减 +36 / -5

执行摘要

修复测试 entry_points mock 误屏蔽 NCCL 后端注册

PR body 指出 fork-path 的 logits processor 测试 helper 在进程范围内替换了 importlib.metadata.entry_points,此前对其他 group 均返回空结果;PyTorch nightly 通过 torch.distributed.backends 注册内置分布式后端,导致 fork 的 engine worker 无法发现 NCCL。关联 issue pytorch/pytorch#192062 用证据排除了“缺 NCCL 构建”的可能:同一 job 中 backend=nccl 成功初始化 20 余次,只有 LOGITPROC_SOURCE_ENTRYPOINT 变体的子进程失败,且 4 个不同 vLLM commit 在 nightly 上稳定复现、stable 全过,锁定根因是 entrypoint 加载路径对 torch.distributed.backends 注册的屏蔽。

建议快速阅读,重点看 tests/v1/logits_processors/utils.py 中“最小化 mock 范围、其余委派原实现”的模式,以及新增测试用哨兵对象验证委派行为的技巧;可配合 pytorch/pytorch#192062 的根因分析理解整个问题链。无需精读,因无生产代码逻辑。

讨论亮点

本 PR 的 review 无实质技术讨论:claude[bot] 因 fork 自动跳过,atalman 与 zou3519 直接 approve。核心论证集中在关联 issue pytorch/pytorch#192062 中,报告者通过“同一 job 内 20+ 次 backend=nccl 初始化成功、仅 entrypoint 变体失败”和“4 个 vLLM commit 在 nightly 稳定失败、stable 全过”的 A/B 实验,排除缺 NCCL 构建的误判,将根因锁定在 torch 2.14 nightly 的 torch.distributed.backends entrypoint 注册与测试 mock 的冲突。

实现拆解

  1. 定位根因:tests/v1/logits_processors/utils.py 中的 fake_entry_points(group) 无条件返回 EntryPoints(group),等价于把除 logitproc 外的所有 entry point group 全部屏蔽;PyTorch 2.14 起 NCCL 等后端通过 torch.distributed.backends group 注册,fork worker 在 init_process_group 时查不到后端即报错。
  2. 修改 setup_fake_entrypoint:删除模块级独立函数 fake_entry_points(group: str),将逻辑内联为闭包 fake_entry_points(**kwargs);先保存 original_entry_points,仅当 group == LOGITSPROCS_GROUP 时返回假的 EntryPoints(group),其余查询(包括不带 group 参数的 entry_points() 全量调用)原样委派给 original_entry_points(**kwargs),避免破坏其他库的 entry point 发现机制。
  3. 新增回归测试:在 tests/v1/logits_processors/test_custom_offline.py 中新增 test_fake_entrypoint_preserves_other_groups,先用 monkeypatchimportlib.metadata.entry_points 替换为返回哨兵对象的 lambda,再强制 requires_spawn_multiprocessing 返回 False 走 fork 路径;随后断言 logitproc group 仍返回 fake EntryPointsnames 正确、其他命名 group 与无参调用均透传哨兵对象。
  4. 验证配套:PR 在 torch 2.14.0.dev20260804+cu130 下跑通 4 条测试命令(新增回归测试、entrypoint 加载端到端、两条拒绝路径)及 pre-commit;无生产代码、配置或 schema 改动。
文件 模块 状态 重要度
tests/v1/logits_processors/utils.py 测试工具 modified 5.15
tests/v1/logits_processors/test_custom_offline.py 日志处理器 modified 4.92

关键符号

fake_entry_points setup_fake_entrypoint test_fake_entrypoint_preserves_other_groups

关键源码片段

tests/v1/logits_processors/utils.py bugfix

核心修复所在:fake_entry_points 由无条件返回 EntryPoints 改为仅替身 logitproc group、其余透传原始实现,解决 torch nightly 下 NCCL 后端注册被屏蔽的问题。

def setup_fake_entrypoint(monkeypatch) -> None:
    """让当前进程与 fork 出的 worker 能发现假 logitproc entrypoint。"""
    # spawn 路径:真实注册一个临时 dist-info 包,靠 PYTHONPATH 传播。
    if requires_spawn_multiprocessing():
        register_fake_entrypoint(monkeypatch)
        monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
        return
​
    import importlib.metadata
​
    # 必须先保存原始实现:PyTorch 等库依赖 entry_points 注册
    # 自己的后端(如 torch.distributed.backends 的 NCCL),
    # 不能把整个 API 都替换成只返回空结果的假实现。
    original_entry_points = importlib.metadata.entry_points
​
    def fake_entry_points(**kwargs):
        # 仅替身 logitproc group,其余 group 与无参调用
        # (entry_points() 全量查询)都透传给原始实现。
        group = kwargs.get("group")
        if group == LOGITSPROCS_GROUP:
            return EntryPoints(group)
        return original_entry_points(**kwargs)
​
    monkeypatch.setattr(importlib.metadata, "entry_points", fake_entry_points)
    monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "fork")
tests/v1/logits_processors/test_custom_offline.py test-coverage

新增回归测试 test_fake_entrypoint_preserves_other_groups,覆盖 PR 要保护的两种行为:非 logitproc group 与无参调用不被 fake 实现吞掉。

def test_fake_entrypoint_preserves_other_groups(monkeypatch):
    # 用哨兵对象占位,验证非 logitproc group 与无参调用
    # 都会被透传(而不是被 fake 实现吞掉)。
    other_group_entrypoints = object()
    monkeypatch.setattr(
        importlib.metadata,
        "entry_points",
        lambda **kwargs: other_group_entrypoints,
    )
    # 强制走 fork 路径(Linux/CPU CI 默认分支),
    # 让 setup_fake_entrypoint 走到 fake_entry_points 的替换逻辑。
    monkeypatch.setattr(
        logitproc_test_utils, "requires_spawn_multiprocessing", lambda: False
    )
​
    setup_fake_entrypoint(monkeypatch)
​
    # 1. logitproc group 仍然返回替换后的假 EntryPoints。
    logitproc_entrypoints = importlib.metadata.entry_points(
        group=logitproc_test_utils.LOGITSPROCS_GROUP
    )
    assert logitproc_entrypoints.names == [
        logitproc_test_utils.DUMMY_LOGITPROC_ENTRYPOINT
    ]
    # 2. 其他命名 group 与无参全量查询都透传给哨兵对象。
    assert (
        importlib.metadata.entry_points(group="another.entrypoint.group")
        is other_group_entrypoints
    )
    assert importlib.metadata.entry_points() is other_group_entrypoints

评论区精华

根因确认:非缺 NCCL 构建,而是 entry_points mock 屏蔽了后端注册 正确性

issue 报告者详细举证:同一 job 中 backend=nccl 成功初始化 20 余次,只有 LOGITPROC_SOURCE_ENTRYPOINT 变体的子进程失败;在四个不同 vLLM commit 上 nightly 稳定复现、stable 全部通过,确认是 torch 2.14 nightly 引入 torch.distributed.backends entrypoint 注册机制与 vLLM 测试 mock 冲突。

结论:vLLM 侧修复:fake_entry_points 仅替身 logitproc group,其余查询委派原实现,由 PR #51097 合并解决。 · 已解决

用哨兵对象验证非目标 group 的委派行为 测试

新增测试先将 importlib.metadata.entry_points 整体替换为返回 other_group_entrypoints 的 lambda,再调用 setup_fake_entrypoint,从而断言 fake 实现会把非 logitproc group 和无参调用原样透传。

结论:该设计已随 PR 合并,作为防止回归的测试覆盖。 · 已解决

风险与影响

  1. 仅测试代码变更:不触碰生产路径,serving 行为与模型输出零风险。
  2. fork 路径行为变化setup_fake_entrypoint 现在捕获的是“调用时刻已被替换的 entry_points”,新测试有意利用这一点做哨兵验证;若未来其他测试在调用前先替换 entry_points,透传行为会以该替换为准,需要在维护时注意。
  3. 签名变化fake_entry_points 从位置参数 group: str 改为 **kwargs 并内联为闭包,本文件内无外部调用者,风险可控。
  4. spawn 路径未覆盖requires_spawn_multiprocessing() 为 True 的平台(如 macOS/Windows)走 register_fake_entrypoint 分支,不受本次修复影响,也未被新增测试覆盖。

CI 层面:修复 torch 2.14 nightly 通道 V1 Sample + Logits 的 5 个失败(离线 entrypoint 变体的 2 个断言失败 + 1 个 ERROR、在线 2 个 ERROR),夜间构建恢复绿色。用户层面:无任何影响,不改变模型输出或服务行为。团队层面:解除了对上游 PyTorch entrypoint 注册机制变化的耦合风险,后续 vLLM 插件加载逻辑可以继续安全依赖标准 entry point 发现机制。

仅测试代码变更 fork 路径 mock 行为变化 依赖 torch entrypoint 注册机制

关联 Issue

#192062 [vllm] [torch nightly 2.14] `AssertionError: Unknown c10d backend type NCCL` in `_new_process_group_helper` when a logits processor is loaded via entrypoint

完整报告

参与讨论