You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Observed on releases/v0.27.1 (6e448d0ea9), reading the same code paths on current main.
Runtime: vLLM 0.27.2.dev1 (source install), torch 2.13.0+cu130, 2x NVIDIA L20, TP=2, Python 3.12.
The issue is scheduling logic, not environment-specific.
🐛 Describe the bug
The v1 LogitsProcessor interface documents output_tok_ids as a live view of each request's generated tokens:
Key assumption: the output_tok_ids list (which is an element of each tuple in added) is a reference to the request's running output tokens list; via this reference, the logits processors always see the latest list of generated output tokens.
Under async scheduling (default-on since 0.27), that assumption only holds conditionally. The worker-side lists are extended with -1 placeholders each step (real ids stay on the GPU in prev_sampled_token_ids):
A repair mechanism exists precisely for this: set_async_sampled_token_ids / update_async_output_token_ids patch the -1s with real ids right before the logits processors run:
# We currently don't know whether a particular custom logits processor# uses output token ids so we set this conservatively. ...logitsprocs_need_output_token_ids=bool(custom_logitsprocs),
So there are three under-/over-approximations of the same unknown:
Builtin-registered processors are never counted. The three in-tree builtins happen to only read the list's length (MinTokensLogitsProcessor), never its values, so nothing in-tree notices. Any stateful processor registered via BUILTIN_LOGITS_PROCESSORS that reads values silently computes on placeholder garbage: full per-step overhead is paid, results are wrong, and there is no warning or error.
Conversely, CLI custom processors that don't read values still force the repair path (the conservative True), paying an unnecessary sync in async mode.
Reproduction / real-world impact
We integrated Google DeepMind's SynthID-Text watermarking (via the HF transformers processor) as a builtin stateful logits processor on top of releases/v0.27.1 — the first "reads values" consumer of this contract we know of:
with --no-async-scheduling: 8/8 watermarked outputs detected, mean-g z-score avg 7.86 (range 5.51–11.47), 0 false positives on controls;
with default async scheduling and identical code: 0/8 detected (z avg 0.39 ≈ control 0.33), while still paying ~11% per-request latency for the watermark compute — the context window degrades to [-1, -1, -1, -1].
Minimal repro sketch: register any processor that prints output_tok_ids from apply() in BUILTIN_LOGITS_PROCESSORS, run with defaults (no penalties, no CLI --logits-processors), and observe [-1, -1, ...]; pass --no-async-scheduling (or any CLI custom processor, which flips the flag) and observe real ids.
Suggested fix
A declarative capability on the interface would solve all three cases at once, mirroring is_argmax_invariant():
classLogitsProcessor(ABC):
@classmethoddefneeds_output_token_ids(cls) ->bool:
returnTrue# conservative default for out-of-tree processors
the three in-tree builtins override it to False (no behavior change, removes today's over-approximation for value-agnostic CLI procs);
build_logitsprocs ORs the declarations across builtin + entry-point + CLI processors and feeds logitsprocs_need_output_token_ids;
the interface.py "Key assumption" docstring gains a note that the live view is only guaranteed when this capability is declared (or async scheduling is off).
Short of that, amending the docstring plus a startup warning when async scheduling is enabled with non-in-tree processors would at least make the failure mode discoverable — today it is completely silent.
Happy to help validate a fix against the watermarking use case; we can also upstream the SynthID processor itself as a separate PR if there is interest.
Your current environment
Observed on
releases/v0.27.1(6e448d0ea9), reading the same code paths on current main.Runtime: vLLM 0.27.2.dev1 (source install), torch 2.13.0+cu130, 2x NVIDIA L20, TP=2, Python 3.12.
The issue is scheduling logic, not environment-specific.
🐛 Describe the bug
The v1
LogitsProcessorinterface documentsoutput_tok_idsas a live view of each request's generated tokens:https://github.com/vllm-project/vllm/blob/6e448d0ea9/vllm/v1/sample/logits_processor/interface.py#L45-L48
Under async scheduling (default-on since 0.27), that assumption only holds conditionally. The worker-side lists are extended with
-1placeholders each step (real ids stay on the GPU inprev_sampled_token_ids):https://github.com/vllm-project/vllm/blob/6e448d0ea9/vllm/v1/worker/gpu_model_runner.py#L3823-L3846
A repair mechanism exists precisely for this:
set_async_sampled_token_ids/update_async_output_token_idspatch the-1s with real ids right before the logits processors run:https://github.com/vllm-project/vllm/blob/6e448d0ea9/vllm/v1/worker/gpu_input_batch.py#L1030-L1056
But the repair is gated by
needs_output_token_ids(penalties / bad_words / thinking budget /logitsprocs_need_output_token_ids):https://github.com/vllm-project/vllm/blob/6e448d0ea9/vllm/v1/worker/gpu_input_batch.py#L908-L922
and the logitsprocs contribution to that gate only counts CLI-passed custom processors:
https://github.com/vllm-project/vllm/blob/6e448d0ea9/vllm/v1/worker/gpu_model_runner.py#L714-L726
So there are three under-/over-approximations of the same unknown:
vllm.logits_processorsgroup) are not counted — being fixed by [Bugfix] Include entry-point logits processor plugins in output token… #38199.MinTokensLogitsProcessor), never its values, so nothing in-tree notices. Any stateful processor registered viaBUILTIN_LOGITS_PROCESSORSthat reads values silently computes on placeholder garbage: full per-step overhead is paid, results are wrong, and there is no warning or error.True), paying an unnecessary sync in async mode.Reproduction / real-world impact
We integrated Google DeepMind's SynthID-Text watermarking (via the HF
transformersprocessor) as a builtin stateful logits processor on top ofreleases/v0.27.1— the first "reads values" consumer of this contract we know of:--no-async-scheduling: 8/8 watermarked outputs detected, mean-g z-score avg 7.86 (range 5.51–11.47), 0 false positives on controls;[-1, -1, -1, -1].Branch with the processor and a fail-safe guard (refuses to watermark and warns when async scheduling is detected): https://github.com/danbo610/vllm/tree/feature/synthid-watermark (see commit
f18c17a3bbfor the guard).Minimal repro sketch: register any processor that prints
output_tok_idsfromapply()inBUILTIN_LOGITS_PROCESSORS, run with defaults (no penalties, no CLI--logits-processors), and observe[-1, -1, ...]; pass--no-async-scheduling(or any CLI custom processor, which flips the flag) and observe real ids.Suggested fix
A declarative capability on the interface would solve all three cases at once, mirroring
is_argmax_invariant():False(no behavior change, removes today's over-approximation for value-agnostic CLI procs);build_logitsprocsORs the declarations across builtin + entry-point + CLI processors and feedslogitsprocs_need_output_token_ids;interface.py"Key assumption" docstring gains a note that the live view is only guaranteed when this capability is declared (or async scheduling is off).Short of that, amending the docstring plus a startup warning when async scheduling is enabled with non-in-tree processors would at least make the failure mode discoverable — today it is completely silent.
Happy to help validate a fix against the watermarking use case; we can also upstream the SynthID processor itself as a separate PR if there is interest.