Skip to content

[Core] Add a generation readiness check /health_generate - #60505

Open
SumanthRH wants to merge 19 commits into
vllm-project:mainfrom
SumanthRH:health-generate-api
Open

SumanthRH wants to merge 19 commits into
vllm-project:mainfrom
SumanthRH:health-generate-api

Conversation

@SumanthRH

@SumanthRH SumanthRH commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Adds a generation health check at /health_generate

The implementation is based on PR #59179, with improvements that cover more edge cases with DP > 1 and report why the server is not ready. I've added the author of #59179 as a committer on this PR. Please see #59179 for context on the initial design.

This does not duplicate other work: it continues #59179, and I'm not aware of another open PR adding a generation health check.

Background

For long running inference deployments for online serving as well as RL use-cases, there can be a variety of inference engine failures or hangs. Broadly there are two sources of failures / hangs:

  1. Infrastructure failures: GPU failures, network failures, etc. Some of these are recoverable (transient error that succeeds on retry) while some are not (GPU failure).
  2. Software bugs: There are two styles of errors here:
    a. Probabilistic, recoverable: Errors that happen due to low-probability events such as:
    1. An illegal memory access with vLLM that occurs once in millions of generations.
    2. A collective hang during weight syncing in DPEP setups.
    3. Generation hang with spec decoding + MoE + DP.
      b. Irrecoverable bugs: Bugs that are deterministic failures in certain state.
    4. Misconfigurations, unsupported kernel on certain hardware, etc.

A generation health check helps users detect and recover from failures of class 1. and 2.a.

List of failures to handle

Here are a few relevant issues for vLLM server failures / hangs:

Issue Brief description Failure mode
#44185 DP + EP + MTP/speculative decoding can hang near max_model_len when DP ranks disagree about whether the drafter path should run. EngineCore forward/collective hang; processes may stay alive while affected requests stop making progress.
#43547 MoE + DP can drift collective sequence after zero-token scheduled steps skip DP coordination, eventually hanging a real MoE collective. EngineCore forward collective hang, likely with worker processes alive but blocked in reduce_scatter or a related collective.
#36594 / #51481 DPEP pause/wake coordination can leave DP ranks or the coordinator in the wrong running state around pause_generation, sleep(), collective_rpc, or online weight update. The older issue covers a late START_DP_WAVE re-arming a paused engine; the later PR fixes the coordinator assuming engines started merely because it forwarded a wake. DP wave/coordinator control-plane hang: processes can stay alive, but drain times out, frontends may stop sending FIRST_REQ, or ranks can enter mismatched collectives during weight-sync/control RPCs.
#53130 Running chunked-prefill work can consume the scheduler token budget every step, so waiting/deferred requests are not admitted or promoted. Admission starvation / request-level timeout, not a full EngineCore hang; existing running requests may still progress.
#54173 Qwen3.8-Flash-Next prefix-caching/GDN path can hit CUBLAS internal error or illegal memory access in fused Mamba/GDN kernels. Kernel crash leading to EngineCore or worker failure after the async CUDA error surfaces.
#37035 MTP/hybrid GDN illegal memory access under load, later tied to a drafter block-size/cache-pressure bug. Kernel crash leading to EngineCore or worker failure; may require long-running/load-heavy traffic to trigger.
#60109 HiSparse + GLM TP8 on B200 hits illegal memory access when a new request wave is admitted after a previous wave completes. Wave-transition kernel crash leading to EngineCore or worker failure.
#59768 SimpleCPUOffloadConnector can hit illegal memory access while async CPU-to-GPU prefix load is in flight. Connector/load-side CUDA race causing EngineCore or worker failure, especially on prefix-cache/offload paths.
#45388 KV-offloading plus prefix caching can deadlock the V1 scheduler under KV-cache pressure: the engine reaches running=0, scheduled=0, waiting requests remain stuck, and throughput stays at zero. Scheduler/request-progress hang with EngineCore process alive.

What kind of health checks will detect such failures / hangs?

Health check Description Failures caught
Existing /health endpoint API server and Engine process liveness. #54173, #37035, #60109, #59768, once the CUDA/kernel failure surfaces as EngineCore or worker death.
/health_generate in SGLang Gets the scheduler to admit a dummy request. If the scheduler is busy (running + waiting > 0), return early with a success response. If the scheduler is idle, execute a dummy forward pass (single token). #44185, #43547, #36594 / #51481, #54173, #37035, #60109, #59768. However, issues like #45388 are not caught - this is because the scheduler can seem busy (running + waiting > 0) when it's not making any progress (running = 0, waiting > 0) for a long time.
Proposed /health_generate in vLLM (SGLang's check plus busy-stall detection) Evaluated per engine rank. If a rank is busy, check how long it has been since it last processed tokens; past a timeout, the server is not ready. If a rank is idle, run a dummy forward pass on it and wait for it to finish on the device. If a rank is sleeping, report it. #44185, #43547, #36594 / #51481, #54173, #37035, #60109, #59768 and #45388.

One failure that is not caught by any of the methods above is #53130. I would argue that it is out of scope for the current effort, since the issue is a bit unclear on the exact root cause for the bug. A part of the issue also mentions that the hang occurs when the scheduler is already busy and is able to make progress on running prefill inputs, which means that the engine is alive and healthy.

Note that we haven't discussed any recoverable errors from infrastructure failures. Typical failures here lead to collective timeouts or EngineCore process death, which are of a similar nature to the failures above.

Changes on top of #59179

Readiness evaluation

  • Per rank. Each engine rank is checked separately:

    • a sleeping rank reports sleeping;
    • a busy rank is checked for stalls;
    • an idle rank gets the GPU probe.

    So a busy DP rank no longer hides an idle rank that is stuck in a control RPC.

  • Progress comes only from the engines. The frontend no longer marks a rank busy when it sends a request. That mark could leave the server reporting stalled while idle, if the engine dropped the request before scheduling it.

  • Connector cleanup is not a stall. Steps that only drain KV-transfer cleanup for finished requests (e.g. a P/D prefill node waiting on a remote pull) count as progress.

  • A dedicated broadcast. EngineCore publishes readiness to every API server in its own message. Nothing is attached to request outputs any more.

The idle GPU probe

  • New check_health_gpu on the worker:
    • It runs the dummy forward pass without adding dummy LoRA adapters or removing loaded ones.
    • It waits for the device to finish before replying, so a fault or hang in the probe's own forward pass fails that same call.
  • Every TP and PP worker must reply. PP stages don't communicate during a dummy run, so a fault on an earlier stage would otherwise go unnoticed.
  • Out-of-tree platforms: WorkerBase.check_health_gpu raises NotImplementedError, so platforms that don't implement it report probe_failed.
  • Shared result:
    • Concurrent callers share one in-flight probe per rank.
    • A successful probe is cached for VLLM_READY_IDLE_PROBE_CACHE_TTL_S.

DP > 1

  • Only count a DP dummy batch as progress when the rank has no local unfinished requests. A rank that holds requests but schedules nothing (e.g. [Bug]: Scheduler permanently deadlocks under KV-cache pressure when a KV-offloading connector is enabled #45388) keeps the wave alive with dummy batches, and was previously reported as making progress.
  • Run the idle probe for DP > 1 instead of skipping it.
    • Dense DP ranks are independent engines and run the probe as with DP = 1.
    • MoE DP engines answer with a liveness-only ping, because a dummy batch outside the wave loop could deadlock against peer collectives. A busy loop stuck in a control RPC now makes the probe time out.

Not-ready details

  • Every not-ready response is a 503 with a JSON body:
    {"status": "not_ready", "reason": ..., "message": ..., <details>, "in_progress": [...]}
    • reason is one of engine_dead, sleeping, stalled, probe_timeout or probe_failed.
    • engine_dead returns only status and reason.
  • in_progress lists the utility call each EngineCore is running, with its rank and elapsed time. EngineCore publishes the operation before running a utility and clears it afterwards, so every API server reports it, whichever one issued the call.

Configuration

Env var Default Meaning
VLLM_HEALTH_CHECK_GPU_TIMEOUT 20 s How long a caller waits for the idle probe before reporting probe_timeout.
VLLM_READY_STALL_TIMEOUT_S 60 s A busy rank with no progress for this long reports stalled. 0 disables the check.
VLLM_READY_IDLE_PROBE_CACHE_TTL_S 10 s How long a successful idle probe is reused. 0 disables the cache.

Scope

  • Rust frontend:
    • The Rust client decodes the new readiness messages and ignores them, so normal Rust serving is unaffected.
    • The Rust /health_generate endpoint, and aggregating it in the DP supervisor, will come in a follow-up PR.
  • DP supervisor: its /ready and /readyz are unchanged.
  • Render-only servers (no engine) return 200.

Why report additional not-ready details?

Downstream applications, such as a load balancer or a health monitor daemon, decide whether to tear down a server based on the response. A 503 from /health_generate does not mean the server is broken; it means the server can't serve generation requests right now. For example, it returns 503 while waiting for a weight sync to complete.

Comparison with the /fault_tolerance/ APIs

The current fault tolerance APIs can detect failures/hangs during the forward pass in DPEP MoE deployments. This PR targets a broader set of failures/hangs, which is why it adds a separate /health_generate endpoint.

Known limitations

  • Probe stuck for a long time, then recovers. If the idle probe is stuck for longer than VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS (300 s) and then completes, its late reply stays queued. The next executor RPC reads it, and the engine fails. The same applies to any timed-out collective_rpc utility call today.
  • VLLM_HEALTH_CHECK_GPU_TIMEOUT <= 0 makes every idle probe report probe_timeout.
  • Rust frontend: /health_generate is not served yet (see Scope above).

Test Plan

Apart from the unit tests added in #59179, I've added real E2E tests that cover failure modes from the list above. I think these E2E tests are important to guarantee failure recovery at the engine level.

New tests:

  • Unit tests for:
    • per-rank evaluation, the DP probe and connector-cleanup progress;
    • shared and timed-out probes;
    • reason and in_progress reporting;
    • the engine-side operation broadcast.
  • Fault-injection E2E tests in tests/v1/engine/readiness, using --scheduler-cls and --worker-extension-cls, split by GPU count:
    • test_ready_single_gpu.py (1 GPU): scheduler stall, a latent CUDA fault while idle (including a fault in the probe's own forward pass), and no false positives.
    • test_ready_multi_gpu.py (2 GPUs): MoE and dense DP = 2 variants, including a rank stuck in a mismatched DP collective, and a PP = 2 CUDA fault on the stage that doesn't return outputs.
    • These run in the V1 Engine and Distributed CI steps.
  • Rust: a check that a Python-encoded readiness message decodes in the Rust client.
pytest tests/v1/engine/test_async_llm_health.py -v
pytest tests/entrypoints/serve/instrumentator/test_basic.py -v
pytest tests/v1/engine/readiness/test_ready_single_gpu.py -v   # 1 GPU
pytest tests/v1/engine/readiness/test_ready_multi_gpu.py -v    # 2 GPUs
cd rust && cargo test -p vllm-engine-core-client

Test Result

Suite Result
tests/v1/engine/test_async_llm_health.py 26/26 passed
tests/entrypoints/serve/instrumentator/test_basic.py 9/9 passed (server_load deselected, as in CI)
tests/v1/engine/readiness/test_ready_single_gpu.py 4/4 passed
tests/v1/engine/readiness/test_ready_multi_gpu.py 5/5 passed
tests/entrypoints/launchers/test_dp_supervisor.py passed (unchanged from main)
Rust vllm-engine-core-client 133/133 passed

AI assistance (Claude Code) was used for this PR. I have reviewed every changed line and ran the tests above.

🤖 Generated with Claude Code


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

liofranxx and others added 4 commits September 15, 2026 09:55
Co-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: liofranxx <liofranxx@99-1-180-7.lightspeed.clmasc.sbcglobal.net>
Resolve readiness-health conflicts while preserving upstream KV event cleanup.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>\nSigned-off-by: liofranxx <liofranxx@99-1-180-7.lightspeed.clmasc.sbcglobal.net>

Signed-off-by: liofranxx <liofranxx@99-1-180-7.lightspeed.clmasc.sbcglobal.net>
DP>1 fixes:
- Only count a DP dummy batch as ready progress when the rank has no
  local unfinished requests. A rank that holds requests but schedules
  nothing (e.g. vllm-project#45388) keeps the wave alive with dummy batches and was
  previously reported as making progress.
- Run the idle readiness probe for DP>1 instead of skipping it. Dense DP
  ranks are independent engines and run the dummy-batch probe as with
  DP=1. MoE DP engines answer with a liveness-only ping, since a dummy
  batch outside the wave loop could deadlock against peer collectives;
  a busy loop wedged in a control RPC now times out the probe.

Not-ready details:
- /ready returns a JSON body for every not-ready case:
  {"status": "not_ready", "reason": ..., "message": ..., <details>,
   "in_progress": [...]} with reason one of engine_dead, sleeping,
  stalled, probe_timeout or probe_failed.
- in_progress lists the utility call each EngineCore is executing, with
  its rank and elapsed time. EngineCore publishes the operation on the
  ready-progress broadcast before running a utility and clears it
  afterwards, so every API server reports it regardless of which one
  issued the call.

Tests:
- Unit tests for the DP probe, reason/in-progress reporting and the
  engine-side operation broadcast.
- Fault-injection E2E tests (tests/v1/engine/readiness) using
  --scheduler-cls / --worker-extension-cls: scheduler stall, latent CUDA
  fault while idle and no-false-positive checks on a single engine, and
  MoE/dense DP=2 variants including a rank wedged in a mismatched DP
  collective. Wired into the V1 Engine and Distributed DP Basic steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@mergify

mergify Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--60505.org.readthedocs.build/en/60505/

@mergify mergify Bot added documentation Improvements or additions to documentation ci/build frontend labels Oct 8, 2026
@mergify

mergify Bot commented Oct 8, 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, @SumanthRH.

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

@mergify mergify Bot added the needs-rebase label Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>

@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.

@SumanthRH

Copy link
Copy Markdown
Contributor Author

/ci run

@mergify mergify Bot removed the needs-rebase label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

❌ @SumanthRH, A reviewer with write access must run /ci run, approve the PR, or add the ready label first.

@SumanthRH

Copy link
Copy Markdown
Contributor Author

RIP

@aoshen02 aoshen02 added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

✅ @SumanthRH, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@aoshen02

aoshen02 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

/ci run

@vllm-agent

Copy link
Copy Markdown
Collaborator

CI selector (shadow): 152 test steps (213 jobs) instead of 86 (106 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 86 (106) 152 (213) 9 (17) 75 (124)
AMD mirrors 83 (113) 136 (191) 8 (13) 61 (91)
Selector would run (152)
  • amd-kernels-mi355
  • amd-lm-eval-small-models-harness
  • amd-native-quantization-kernels-mi355
  • amd-qwen3-next-mtp-async-eplb-accuracy
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-initialization
  • basic-models-tests-other
  • batch-invariance-b200
  • batch-invariance-h100
  • benchmarks-cli-test
  • cpu-multi-modal-model-tests-n ×4
  • cpu-multimodal-config
  • cpu-params-env-tokenizers-parser
  • cpu-reasoning-renderers
  • cpu-tool-parsers
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • cudagraph
  • distributed-comm-ops
  • distributed-compile-comm-4-gpus
  • distributed-compile-rpc-tests-2-gpus
  • distributed-compile-unit-tests-2xh100
  • distributed-dp-tests-2-gpus
  • distributed-dp-tests-4-gpus
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus
  • distributed-model-tests-2-gpus ×3
  • distributed-mooncakeconnector-pd-accuracy-4-gpus
  • distributed-nixlconnector-pd-accuracy-4-gpus
  • distributed-tests-8xh100
  • distributed-torchrun-examples-4-gpus
  • distributed-torchrun-shutdown-tests-2-gpus
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • e2e-core-1-gpu
  • e2e-core-large-memory
  • e2e-scheduling-1-gpu
  • e2e-scheduling-accuracy-1-gpu
  • elastic-ep-scaling-test
  • engine
  • engine-1-gpu
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • entrypoints-unit-tests
  • examples
  • extract-hidden-states-integration
  • extract-hidden-states-integration-2-gpus
  • fault-tolerance-e2e-2xh100
  • fusion-and-compile-unit-tests-2xb200
  • fusion-e2e-config-sweep-h100
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-ar-rms-dynamo-partition-amd
  • fusion-e2e-tp2-ar-rms-inductor-partition-amd
  • fusion-e2e-tp2-asynctp-config-sweep-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • gemm-rs-ar-2xb200
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus
  • kernels-attention-test ×7
  • kernels-b200 ×3
  • kernels-core-operation-test ×3
  • kernels-deepgemm-test-h100
  • kernels-mamba-test
  • kernels-moe-test ×5
  • kernels-quantization-test ×6
  • kernels-root-misc-test-b200
  • kimi-k3-prefix-cache-4xb200 ×2
  • kv-offload-large
  • kv-offload-medium
  • kv-offload-small
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-dspark-watermark-2xh100
  • lm-eval-small-models
  • lm-eval-turboquant-k3v4nc
  • lm-eval-turboquant-k8v4
  • lm-eval-turboquant-t3nc
  • lm-eval-turboquant-t4nc
  • lm-eval-watermarking
  • lora ×4
  • lora-tp-distributed ×4
  • metrics-tracing-2-gpus
  • model-executor
  • mooncake-ec-tcp-e2e-2-gpus
  • mrcr-eval-small-models
  • multi-modal-accuracy-eval-small-models
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus
  • nixlconnector-pd-edge-cases-2-gpus
  • openai-api-correctness
  • pipeline-context-parallelism-4-gpus
  • plugin-tests-2-gpus
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • quantization ×4
  • quantized-models-test
  • quantized-moe-test-b200
  • rayexecutorv2-4-gpus
  • regression
  • replayssm-e2e
  • rl-entrypoints-tests
  • rust-frontend-core-correctness
  • rust-frontend-distributed
  • rust-frontend-openai-coverage
  • rust-frontend-serve-admin-coverage
  • rust-frontend-tool-use
  • samplers-multimodal-beam-search
  • samplers-test
  • scale-out-ec-e2e-2-gpus
  • sharded-rdt-weight-transfer
  • spec-decode-draft-model ×4
  • spec-decode-eagle-1-deepseek-qwen
  • spec-decode-eagle-2-llama3-qwen-vl-other
  • spec-decode-mtp-deepseek-mimo
  • spec-decode-mtp-gemma4
  • spec-decode-mtp-qwen3-5
  • spec-decode-ngram-suffix
  • spec-decode-speculators
  • v1-attention-b200 ×2
  • v1-attention-h100-mi300 ×2
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-others-cpu
  • v1-sample
  • v1-spec-decode
Would skip (today's rules run them) (9)
  • amd-fp8-moe-kernels-mi355
  • ascend-npu-test
  • basic-models-test-other-cpu
  • cpu-language-generation-and-pooling-model-tests ×3
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • platform-tests
Would add (today's rules do not run them) (75)
  • amd-kernels-mi355 (Python record)
  • amd-lm-eval-small-models-harness (Python record)
  • amd-qwen3-next-mtp-async-eplb-accuracy (Python record)
  • basic-models-tests-extra-initialization ×14 (code map)
  • batch-invariance-b200 (code map)
  • batch-invariance-h100 (code map)
  • cpu-multi-modal-model-tests-n ×4 (code map)
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • cudagraph (code map)
  • distributed-compile-unit-tests-2xh100 (code map)
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus (Python record)
  • distributed-model-tests-2-gpus ×3 (code map)
  • distributed-mooncakeconnector-pd-accuracy-4-gpus (Python record)
  • distributed-nixlconnector-pd-accuracy-4-gpus (Python record)
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • extract-hidden-states-integration (Python record)
  • extract-hidden-states-integration-2-gpus (Python record)
  • fusion-and-compile-unit-tests-2xb200 (code map)
  • fusion-e2e-config-sweep-h100 (Python record)
  • fusion-e2e-quick-h100 (Python record)
  • fusion-e2e-tp2-ar-rms-config-sweep-h100 (Python record)
  • fusion-e2e-tp2-ar-rms-dynamo-partition-amd (Python record)
  • fusion-e2e-tp2-ar-rms-inductor-partition-amd (Python record)
  • fusion-e2e-tp2-asynctp-config-sweep-h100 (Python record)
  • fusion-e2e-tp2-b200 (Python record)
  • fusion-e2e-tp2-quick-h100 (Python record)
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus (Python record)
  • kernels-attention-test ×7 (code map)
  • kernels-b200 ×3 (code map)
  • kernels-core-operation-test ×3 (code map)
  • kernels-deepgemm-test-h100 (Python record)
  • kernels-mamba-test (code map)
  • kernels-quantization-test ×6 (code map)
  • kimi-k3-prefix-cache-4xb200 ×2 (Python record)
  • kv-offload-large (Python record)
  • kv-offload-medium (Python record)
  • kv-offload-small (Python record)
  • language-models-tests-extra-standard ×2 (Python record)
  • lm-eval-dspark-watermark-2xh100 (code map)
  • lm-eval-small-models (Python record)
  • lm-eval-turboquant-k3v4nc (Python record)
  • lm-eval-turboquant-k8v4 (Python record)
  • lm-eval-turboquant-t3nc (Python record)
  • lm-eval-turboquant-t4nc (Python record)
  • lm-eval-watermarking (Python record)
  • lora ×4 (code map)
  • lora-tp-distributed ×4 (Python record)
  • model-executor (code map)
  • mrcr-eval-small-models (Python record)
  • multi-modal-accuracy-eval-small-models (Python record)
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus (Python record)
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus (Python record)
  • nixlconnector-pd-edge-cases-2-gpus (Python record)
  • openai-api-correctness (Python record)
  • plugin-tests-2-gpus (code map)
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus (Python record)
  • quantization ×4 (code map)
  • quantized-models-test (Python record)
  • quantized-moe-test-b200 (Python record)
  • rust-frontend-core-correctness (Python record)
  • rust-frontend-tool-use (Python record)
  • samplers-multimodal-beam-search (Python record)
  • samplers-test (Python record)
  • scale-out-ec-e2e-2-gpus (Python record)
  • spec-decode-draft-model ×4 (code map)
  • spec-decode-eagle-1-deepseek-qwen (Python record)
  • spec-decode-eagle-2-llama3-qwen-vl-other (Python record)
  • spec-decode-mtp-deepseek-mimo (Python record)
  • spec-decode-mtp-gemma4 (Python record)
  • spec-decode-mtp-qwen3-5 (Python record)
  • spec-decode-ngram-suffix (Python record)
  • spec-decode-speculators (Python record)
  • v1-attention-b200 ×2 (code map)
  • v1-attention-h100-mi300 ×2 (code map)
AMD mirrors: would skip (8)
  • deepseek-v4-kernel-test-b200
  • fault-tolerance-e2e-2xh100
  • kernels-b200 ×3
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-minimax-reduce-rms-test-2-gpus
  • multi-modal-processor ×4
  • platform-tests
AMD mirrors: would add (61)
  • basic-models-tests-extra-initialization ×14 (code map)
  • batch-invariance-h100 (code map)
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • cudagraph (code map)
  • distributed-compile-unit-tests-2xh100 (code map)
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus (Python record)
  • distributed-model-tests-2-gpus ×3 (code map)
  • distributed-mooncakeconnector-pd-accuracy-4-gpus (Python record)
  • distributed-nixlconnector-pd-accuracy-4-gpus (Python record)
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • extract-hidden-states-integration (Python record)
  • extract-hidden-states-integration-2-gpus (Python record)
  • fusion-and-compile-unit-tests-2xb200 (code map)
  • fusion-e2e-config-sweep-h100 (Python record)
  • fusion-e2e-quick-h100 (Python record)
  • fusion-e2e-tp2-ar-rms-config-sweep-h100 (Python record)
  • fusion-e2e-tp2-asynctp-config-sweep-h100 (Python record)
  • fusion-e2e-tp2-b200 (Python record)
  • fusion-e2e-tp2-quick-h100 (Python record)
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus (Python record)
  • kernels-core-operation-test ×3 (code map)
  • kernels-mamba-test (code map)
  • kv-offload-large (Python record)
  • kv-offload-medium (Python record)
  • kv-offload-small (Python record)
  • language-models-tests-extra-standard ×2 (Python record)
  • lm-eval-dspark-watermark-2xh100 (Python record)
  • lm-eval-small-models (Python record)
  • lm-eval-turboquant-k3v4nc (Python record)
  • lm-eval-turboquant-k8v4 (Python record)
  • lm-eval-turboquant-t3nc (Python record)
  • lm-eval-turboquant-t4nc (Python record)
  • lm-eval-watermarking (Python record)
  • lora ×4 (code map)
  • lora-tp-distributed ×4 (Python record)
  • model-executor (code map)
  • mrcr-eval-small-models (Python record)
  • multi-modal-accuracy-eval-small-models (Python record)
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus (Python record)
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus (Python record)
  • nixlconnector-pd-edge-cases-2-gpus (Python record)
  • nixlconnector-pd-spec-decode-acceptance-2-gpus (Python record)
  • openai-api-correctness (Python record)
  • plugin-tests-2-gpus (code map)
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus (Python record)
  • quantization ×4 (code map)
  • quantized-models-test (Python record)
  • rust-frontend-core-correctness (Python record)
  • rust-frontend-tool-use (Python record)
  • samplers-multimodal-beam-search (Python record)
  • samplers-test (Python record)
  • scale-out-ec-e2e-2-gpus (Python record)
  • spec-decode-draft-model ×4 (code map)
  • spec-decode-eagle-1-deepseek-qwen (Python record)
  • spec-decode-eagle-2-llama3-qwen-vl-other (Python record)
  • spec-decode-mtp-deepseek-mimo (Python record)
  • spec-decode-mtp-gemma4 (Python record)
  • spec-decode-mtp-qwen3-5 (Python record)
  • spec-decode-ngram-suffix (Python record)
  • spec-decode-speculators (Python record)

21 changed files · base 8352b2427f · head 738c565e9b · Python record: build 93039 at 1e5d0ea888 · kernel record: table 43b4aae (build 93244), map 43b4aae · not counted: 10 build steps, 5 A100 steps the generator no longer emits, 129 optional steps the selector would also run

Comment thread vllm/v1/engine/core_client.py Outdated
if progress.operation is not None
]

def _mark_engine_busy(self, engine: EngineIdentity) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This mark has no undo path, so /ready can stay at stalled on an idle, healthy server.

If the EngineCore input thread drops the request (preprocess_add_request raising, or a P0/P1 multimodal cache miss), it never reaches the busy loop, the error output carries no readiness fields, and the engine publishes nothing. _check_ready then sees BUSY, skips the idle probe, and returns stalled after VLLM_READY_STALL_TIMEOUT_S. It stays that way until another request or utility call comes in. Behind a Kubernetes readiness probe it never recovers: the pod is pulled, so no request arrives to fix the state.

Suggestion: update _ready_progress only from engine messages and drop this mark. The early return in _check_ready currently relies on it to catch a wedged idle rank while another DP rank is busy, so this should go together with per-rank evaluation: progress check for BUSY ranks, idle probe for IDLE ranks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed!

SumanthRH and others added 5 commits October 8, 2026 20:12
EngineCore now broadcasts readiness progress to every frontend. The Rust
client decoded EngineCoreOutputs as a fixed 8-field tuple and rejected
readiness-only messages, so the decode error closed all request and
utility registries on the first IDLE->BUSY transition.

- Decode ready_progress_seq, ready_state and ready_operation.
- Classify readiness-only messages as EngineCoreOutputs::Readiness; the
  fields are ignored on request, utility and DP control payloads.
- The output dispatcher ignores readiness until the Rust frontend serves
  /ready.
- Mirror the fields in python_compat.py and check that Python-encoded
  readiness-only and readiness-carrying request frames decode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
The frontend marked an engine BUSY when it sent a request. If the engine
dropped the request before the busy loop (preprocessing error, MM cache
miss), nothing undid the mark and /ready reported stalled until another
request arrived, which never happens behind a readiness probe.
/ready also skipped the idle probe as soon as any rank was BUSY, so a
dense DP rank wedged while IDLE went unnoticed.

- Track readiness only from EngineCore messages; drop _mark_engine_busy.
  The engine-side guard in check_health_gpu already makes a probe that
  races a new request a no-op.
- Check BUSY ranks for stalls and probe every IDLE rank, sharing one
  in-flight probe per rank. Probe failures report the engine ranks.
- Ignore readiness from engines that are no longer tracked, so ranks
  paused on their way out of an elastic EP scale-down cannot leave
  /ready sleeping.
- Drop the probe-task lock; task creation never suspends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
…or it

The idle probe reused execute_dummy_batch, whose LoRA warmup context adds
dummy adapters and calls remove_all_adapters() on exit, so polling /ready
on an idle --enable-lora server evicted every loaded adapter. The probe
also returned once its kernels were queued, so a fault in the forward
pass (or one queued just before it) was not reported by that probe.

- Add Worker.check_health_gpu: a dummy batch under keep_loaded_loras(),
  which skips dummy adapter setup and removal, followed by a device
  synchronize. Executors expose it and EngineCore's probe uses it; DP
  wave dummy batches are unchanged.
- The latent fault E2E test now requires the first /ready after the
  fault to fail, with a variant that faults inside the probe's forward
  pass. Both fail without the synchronize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Scheduler.has_requests() stays true for finished requests awaiting
delayed KV connector frees and for pending connector pushes, so such an
engine stays BUSY while scheduling no tokens and /ready reported it as
stalled, e.g. a P/D prefill node waiting for its decode peer to pull.

Count a step as progress when no unfinished requests remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Readiness fields were stamped onto request outputs in addition to the
standalone broadcast, which is always queued first and reaches every
frontend. Drop the stamping so request outputs keep their original shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@SumanthRH
SumanthRH requested a review from BugenZhao as a code owner October 9, 2026 00:30
@mergify mergify Bot added the rust label Oct 9, 2026
SumanthRH and others added 3 commits October 9, 2026 05:54
…by GPU count

  - Out-of-tree workers have no check_health_gpu; WorkerBase now raises
    NotImplementedError so /ready reports that instead of an AttributeError.
  - Rename the readiness E2E tests to test_ready_single_gpu.py and
    test_ready_multi_gpu.py so the 2-GPU CI step runs one file.

Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
griffe warned that the documented `details` parameter had no annotation,
which fails `mkdocs build --strict`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
The multiproc executor read the probe's reply only from the output rank
(TP0 of the last PP stage). PP dummy runs do not communicate across
stages, so a CUDA fault on an earlier stage left /ready at 200 until the
next request failed. Every rank now replies.

Add a PP=2 E2E test with the fault on the non-output stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>

@ShuoleiWang ShuoleiWang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: the tests could be shorter without losing coverage, e.g. a shared EngineCoreProc setup in test_async_llm_health.py, one parametrized test for the busy/sleeping/DP-wave cases that skip the dummy batch, and the three mocked /ready handler tests in test_basic.py merged into one parametrized test that still checks reason and the JSON body.

def is_ready(self) -> bool:
return self._is_ready and not self._shutdown_event.is_set()

async def check_ready(self) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When the supervisor runs Rust children (VLLM_USE_RUST_FRONTEND), every child returns 404 for /ready, so the supervisor's existing /ready and /readyz stay at 503 even when all children are healthy. The new test_check_ready in test_basic.py hits the same gap in the Rust frontend CI job, which runs that file with VLLM_USE_RUST_FRONTEND=1.

Since Rust /ready is planned as a follow-up anyway, the simplest fix is to leave both out of this PR: keep the supervisor's /ready as it was, and drop test_check_ready, which the readiness e2e tests already cover. Supervisor aggregation can come with the Rust endpoint.

@mergify

mergify Bot commented Oct 11, 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, @SumanthRH.

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

@mergify mergify Bot added the needs-rebase label Oct 11, 2026
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@mergify mergify Bot removed the needs-rebase label Oct 11, 2026
SumanthRH and others added 2 commits October 12, 2026 18:59
The Rust frontend has no /ready yet, so supervisor aggregation reported
503 with Rust children, and test_check_ready failed in the Rust frontend
CI job. Both move to the Rust /ready follow-up; the readiness E2E tests
already cover a healthy server returning 200.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
The DP supervisor already serves /ready and /readyz to mean "the process
is up". /health_generate says what the check does and matches SGLang's
name for the same probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@SumanthRH SumanthRH changed the title [Core] Add a generation readiness check /ready [Core] Add a generation readiness check /health_generate Oct 13, 2026
@mergify

mergify Bot commented Oct 16, 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, @SumanthRH.

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

…ing state

Address review feedback on the core footprint and test size.

- EngineCore.check_health_gpu(cache_ttl_s, stall_timeout_s) now returns the
  rank's health state: sleeping, stalled, busy, or ok after the GPU probe.
  A last-progress timestamp, restarted whenever the engine becomes busy,
  replaces the progress counter.
- get_current_operation is answered by the EngineCore input thread (like
  handle_fault_tolerance), so in_progress is reported while the busy loop
  is blocked inside a utility call.
- Remove the readiness broadcast, the EngineCoreOutputs readiness fields,
  the per-rank client state and its DP rank syncing, and the Rust changes.
- AsyncLLM asks every engine rank and aggregates the answers.
- Use WorkerBase.synchronize_device() in the GPU worker probe.
- Collapse the unit and handler tests into parametrized tests, and add an
  E2E test for a blocked utility call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@dunjin

dunjin commented Oct 26, 2026

Copy link
Copy Markdown

Context: while evaluating this design I prototyped a much smaller opt-in variant, and I'm deliberately not opening a competing PR (repo policy on duplicate work) — posting it here in case any of it is useful for this PR.

Branch: https://github.com/dunjin/vllm/tree/feat/health-generate-probe
Diff vs main: main...dunjin:vllm:feat/health-generate-probe (~600 LOC incl. tests, 9 files)

Approach:

  • GET /health_generate and GET /health?generate=true share one handler; plain /health stays cheap liveness, generation is opt-in, so existing k8s/sidecar probes never trigger GPU work.
  • The probe sends one real request through AsyncLLM.generate (max_tokens=1, min_tokens=1, temperature=0, ignore_eos=True) and reports ready only after a token is produced. The timeout cancels the generator, which reuses generate()'s existing abort path, so no request is leaked.
  • 503 body carries {status, reason, message, request_id} with reason ∈ generation_timeout / no_token_generated / generation_failed / engine_dead / probe_error; budget is VLLM_HEALTH_GENERATE_TIMEOUT_S (30s) or ?timeout=.
  • No EngineCore protocol or Rust wire changes. Role-agnostic for P/D: the prompt is 1 token, so an instance proves itself with a local prefill + one decode step without waiting for its peer.

Known trade-off, same as the #24491 discussion: the probe enqueues a request, so a deep queue can produce a false 503 — that's exactly why it is opt-in and separate from /health. Not covered: DP rank fan-out, and busy-but-stalled detection (which this PR's dummy-batch/progress path handles better).

Happy to fold any part into this PR, or drop it entirely — your call.

This branch has not been deployed

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

Labels

ci/build documentation Improvements or additions to documentation frontend needs-rebase ready ONLY add when PR is ready to merge/full CI is needed rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants