Skip to content

[Bugfix][CI] Give cpu_test a gloo dist_init so it cannot poison forked engines - #56490

Closed
Thangnguyenvn98 wants to merge 1 commit into
vllm-project:mainfrom
Thangnguyenvn98:agent/cpu-test-dist-init-gloo
Closed

Thangnguyenvn98 wants to merge 1 commit into
vllm-project:mainfrom
Thangnguyenvn98:agent/cpu-test-dist-init-gloo

Conversation

@Thangnguyenvn98

Copy link
Copy Markdown
Contributor

What

dist_init always creates an NCCL process group. For a test marked
cpu_test that is both unnecessary and harmful: it initializes the CUDA driver
in the pytest process while leaving torch.cuda.is_initialized() False.

_maybe_force_spawn() (vllm/utils/system_utils.py:126) gates on that flag, so
it keeps the default fork start method. The next test that launches an
EngineCore subprocess without first initializing CUDA in the parent then
inherits a dirty driver and dies:

torch.AcceleratorError: CUDA error: initialization error   (cudaErrorInitializationError)
  at torch._C._cuda_init()   <-  vllm/v1/worker/gpu_worker.py:421 init_device

This change makes dist_init honour the marker the test already carries: a
cpu_test gets a gloo group and skips the accelerator teardown, so it leaves
the CUDA driver untouched.

cpu_only = request.node.get_closest_marker("cpu_test") is not None
backend = "gloo" if cpu_only else "nccl"

Why it is invisible today

Unsharded runs mask it. In nightly build 88259 the poisoner
(test_gemma.py::test_checkpoint_lm_head_can_override_tied_config) is test #21
of 43, behind 20 test_common.py::test_models cases. Those use hf_runner,
which loads a transformers model onto the GPU in the parent — CUDA becomes
initialized, _maybe_force_spawn() switches to spawn, and every later engine
launch is safe.

A 30-day scan of failed jobs (10,669) found zero occurrences in the
unsharded configuration:

step failures this signature
language-models-test-extended-generation, unsharded 26 0 (ordinary regressions)
same, build 88240 shard 4/5 1 1 — only occurrence
language-models-tests-hybrid 197 (12 sampled) 0 (-m hybrid_model never selects it)

Once the suite is sharded it stops being masked. The poisoner and the three
test_dummy_loader cases all live in test_gemma.py, so whenever they share a
shard they run effectively adjacent with no hf_runner test ahead of them:

shards 4 5 6 8
chance they collide 57% 49% 42% 33%

Both observed draws match: build 88240 (N=5) collided and failed; build 88231
(N=8) separated and passed.

Only test_dummy_loader is vulnerable — it uses load_format="dummy" and goes
straight to vllm_runner, so nothing initializes CUDA in the parent first. In
88231 shard 2 the poisoner was also test #1, followed immediately by
test_granite.py::test_models, and that passed because of its hf_runner.

Scope

Seven tests carry both cpu_test and dist_init (found by AST scan). All are
CPU-only weight-loading/tying unit tests with no GPU collectives, so gloo at
world_size=1 is equivalent:

tests/models/test_utils.py::test_module_skip_tied_weights
tests/models/test_utils.py::test_module_skip_tied_weights_without_canonical
tests/model_executor/model_loader/test_weight_tying.py::test_retie_only_when_identical
tests/model_executor/model_loader/test_weight_tying.py::test_quantized_lm_head_is_left_alone
tests/model_executor/model_loader/test_weight_tying.py::test_no_retie_without_checkpoint_override
tests/models/language/generation/test_gemma.py::test_checkpoint_lm_head_can_override_tied_config

tests/v1/attention/test_mla_backends.py and
tests/v1/kv_connector/unit/test_bidirectional_kv_transfer.py use both markers
but on different tests, so they are unaffected. Non-cpu_test users of
dist_init keep NCCL and the full teardown — unchanged.

Adds one guard test asserting the backend is gloo.

Not a duplicate

gh pr list --repo vllm-project/vllm --state open --search "dist_init",
"cpu_test cuda initialization" and "cudaErrorInitializationError" return no
PR touching this fixture or this failure mode. Related prior art: #53170, which
introduced the cpu_test + dist_init combination on test_gemma.py.

Tests run

ruff check    tests/conftest.py tests/model_executor/model_loader/test_weight_tying.py   # pass (pinned v0.14.0)
ruff format   (same files)                                                               # pass
py_compile    (same files)                                                               # pass

Not yet run on GPU — no CUDA available to the author's local environment.
The reproducer is one command, and both tests sit in one file in the failing
order (line 24 before line 121), so it is deterministic:

pytest -v tests/models/language/generation/test_gemma.py

Expected: fails on main, passes with this change. Please confirm on a GPU
runner before merge.

Model evaluation

Not applicable. Test-fixture-only change; no vLLM runtime, serving, or model
output path is touched.

AI assistance

AI assistance (Claude Code) was used to diagnose the failure from Buildkite
logs, to scan 30 days of job history, and to draft this change. The diff is
small and has been reviewed line by line by the submitter.

🤖 Generated with Claude Code

…d engines

The dist_init fixture always creates an NCCL process group. That initializes
the CUDA driver in the pytest process, but leaves torch.cuda.is_initialized()
False. _maybe_force_spawn() gates on that flag, so it keeps the default fork
start method, and the next test to launch an EngineCore subprocess without
first initializing CUDA in the parent dies with:

    torch.AcceleratorError: CUDA error: initialization error

Tests marked cpu_test now get a gloo group and skip the accelerator teardown,
so they leave the CUDA driver untouched. Seven tests carry both cpu_test and
dist_init; all are CPU-only weight-loading/tying unit tests with no GPU
collectives, so gloo at world_size=1 is equivalent for them.

Unsharded runs mask this: engine tests using hf_runner run first, initialize
CUDA in the parent, and force spawn for everything after. It surfaces once the
suite is sharded and a shard puts the cpu_test ahead of its first engine test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtYAn47ncwudr3P9AvcdNt
Signed-off-by: Thang Nguyen <thang.nguyen@inferact.ai>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@Thangnguyenvn98

Copy link
Copy Markdown
Contributor Author

@khluu flagging you since #53170 introduced the cpu_test + dist_init combination on test_gemma.py — no criticism implied, the interaction only becomes reachable under sharding.

Short version: dist_init always builds an NCCL group, which initializes the CUDA driver but leaves torch.cuda.is_initialized() False. _maybe_force_spawn() gates on that flag, so it keeps fork, and the next test that launches an EngineCore subprocess without an hf_runner ahead of it dies with cudaErrorInitializationError.

It has never fired unsharded — 30-day scan of 10,669 failed jobs found 0 occurrences in the unsharded Extended Generation job (26 failures, all ordinary regressions) and exactly 1 in build 88240 shard 4/5. Unsharded runs are protected by the 20 hf_runner tests that run ahead of it and force spawn. Under sharding that protection disappears: the collision chance is ~49% at 5 shards, ~42% at 6, and both real draws match (88240 at N=5 failed, 88231 at N=8 passed).

One ask: I could not run this on GPU. The reproducer is deterministic since both tests are in one file in the failing order — pytest -v tests/models/language/generation/test_gemma.py should fail on main and pass here. Would appreciate a confirmation on a GPU runner before merge.

@mergify mergify Bot added the bug Something isn't working label Sep 11, 2026
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mergify

mergify Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @Thangnguyenvn98.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working needs-rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant