Skip to content

[Bugfix][Rust Frontend] Tolerate NaN-corrupted logprobs in engine-core - #51026

Merged
BugenZhao merged 2 commits into
vllm-project:mainfrom
almersawi:fix/rust-frontend-nan-logprobs-rank-0
Sep 16, 2026
Merged

BugenZhao merged 2 commits into
vllm-project:mainfrom
almersawi:fix/rust-frontend-nan-logprobs-rank-0

Conversation

@almersawi

@almersawi almersawi commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Fix a total-outage bug in the Rust frontend's engine-core client: a single request asking for logprobs/prompt_logprobs while the model produces NaN logits permanently kills the client — /health returns 503 and every subsequent request (including ones with no logprobs) fails with engine-core error: messagepack ext value decode failed: token_ranks must be >= 1 for decoded engine-core logprobs until process restart.

Rank 0 is an engine output that must be accepted: the sampler computes the rank as (logprobs >= sampled_logprob).sum(-1) (vllm/v1/sample/sampler.py), which is 0 when the sampled logprob is NaN because every >= comparison against NaN is false. The decode failure is frame-scoped (one bad row fails the whole batched EngineCoreOutputs payload), happens on the shared output-reader path, and is recorded as a sticky health error, so a per-request data anomaly becomes a server-wide outage.

Fix: in PositionLogprobs::from_decoded_row, accept and preserve sampled_rank = 0 along with the engine's original logprob values. This keeps decoding valid for the other requests in the batch. The existing Rust OpenAI serving helper converts NaN and negative infinity to -9999.0 when constructing HTTP responses; engine-core decoding preserves the numerical results for all consumers.

Why this does not duplicate existing PRs:

Healthy payloads retain their existing behavior. Model evaluation N/A: this changes wire-payload decode tolerance while preserving the engine's numerical output.

AI assistance was used for the original fix and the follow-up changes and tests. The follow-up preserves rank 0 and raw engine values and adds coverage for the existing HTTP conversion.

Test Plan

Two regression tests cover the layer boundary:

  1. zero_sampled_rank_does_not_fail_frame_mates decodes a shared EngineCoreOutputs batch, preserving rank 0, NaN, and negative infinity in both sample and prompt logprobs while keeping the healthy request intact.
  2. chat_logprobs_clamps_nan_and_negative_infinity_for_json verifies that the existing OpenAI response conversion emits -9999.0 for NaN and negative infinity while preserving a finite logprob.

Test Result

  • cargo nextest run --locked -p vllm-engine-core-client -p vllm-server -E 'package(vllm-engine-core-client) | test(routes::openai::utils::logprobs::tests)': 118 passed.
  • cargo clippy --locked -p vllm-engine-core-client -p vllm-server --all-targets --all-features -- -D warnings: passed.
  • cargo fmt --all -- --check, git diff --check, and commit-time pre-commit hooks: passed.

Commands ran from rust/ with a fresh short TMPDIR for tests and an existing Cargo target directory. No GPU was required.

Model evaluation N/A: the change preserves the engine's numerical output and adjusts frontend decode tolerance.


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. (N/A — internal decode-tolerance fix, no docs affected)

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

@mergify mergify Bot added rust bug Something isn't working labels Aug 4, 2026
@almersawi almersawi changed the title [Bugfix][Rust Frontend] Tolerate NaN-corrupted logprobs in engine-cor… [Bugfix][Rust Frontend] Tolerate NaN-corrupted logprobs in engine-core Aug 4, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 257301a689

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@almersawi

Copy link
Copy Markdown
Contributor Author

@BugenZhao Did you have any chance to check this?

…e client

The sampler computes a sampled token's vocab rank as
`(logprobs >= sampled_logprob).sum(-1)`, which is 0 when the sampled
logprob is NaN because every IEEE-754 comparison against NaN is false.
The engine-core client treated rank 0 as a corrupt wire payload and
bailed during decode of the *batched* EngineCoreOutputs frame, which
tripped the shared output-reader path and made the client record a
permanent health error - killing every in-flight and subsequent request
until process restart.

Clamp rank 0 to the lowest valid rank, and map non-finite logprob
values to the same -9999.0 sentinel the Python frontend uses (vllm-project#48585),
so a NaN state degrades to one request's model-quality problem instead
of a server outage.

Tests: `cargo test -p vllm-engine-core-client` (100 passed), including
new cases pinning rank-0 clamping, NaN-sentinel mapping, and survival
of co-scheduled requests sharing the bad row's frame.

Co-authored-by: Kimi (AI coding assistant)
Signed-off-by: Islam <islam.almersawi@openinnovation.ai>

@BugenZhao BugenZhao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Rest LGTM. Thanks!

Comment on lines +69 to +76
// Map non-finite logprobs (from corrupt logits) to the same -9999.0
// sentinel the Python frontend uses, so the serving layer can still
// serialize the response.
let logprob = if logprob.is_finite() {
logprob
} else {
-9999.0
};

@BugenZhao BugenZhao Sep 16, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is not necessary? We can preserve the original value within the frontend pipeline, but only clamp it when it's going to be serialized in the serving layer, which we already have:

pub fn clamp_logprob(logprob: f32) -> f32 {
logprob.max(-9999.0)
}

Keep rank 0 and non-finite engine logprobs intact, leaving sentinel conversion at the OpenAI response boundary. Cover sample and prompt logprobs in a shared output batch and verify the existing HTTP NaN/negative-infinity conversion.

Co-authored-by: Codex
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
@BugenZhao
BugenZhao force-pushed the fix/rust-frontend-nan-logprobs-rank-0 branch from 257301a to 3344086 Compare September 16, 2026 02:40
@BugenZhao

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #89228 for commit 3344086da2c0.

@BugenZhao BugenZhao added the ready ONLY add when PR is ready to merge/full CI is needed label Sep 16, 2026
@BugenZhao
BugenZhao enabled auto-merge (squash) September 16, 2026 02:48
@github-actions

Copy link
Copy Markdown

✅ @almersawi, 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.

@BugenZhao
BugenZhao merged commit c16bb60 into vllm-project:main Sep 16, 2026
37 of 38 checks passed
MK-BK pushed a commit to MK-BK/vllm that referenced this pull request Sep 23, 2026
vllm-project#51026)

Co-authored-by: Bugen Zhao <i@bugenzhao.com>
Signed-off-by: Islam <islam.almersawi@openinnovation.ai>
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working 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.

2 participants