Skip to content

Commit fd9af7d

Browse files
committed
fix(sample): widen the logprobs=-1 sentinel at admission (#231)
`logprobs=-1` -- a legal, validated value meaning "every vocab entry" -- crashed the engine. Any request carrying it died with SIGSEGV inside `LogprobsProcessor::UpdateSampleLogprobs`. The obvious reading blames the sampler, which has a `num_logprobs == -1` arm returning a raw-vocab `LogprobsTensors` with EMPTY ids and ranks while the consumer indexes all three arrays. That reading is wrong, and it is why this commit is not a change to either of those files. `sampler.cpp:343-352` is a faithful port of `sampler.py:122-125` -- the pinned vLLM has exactly that arm. What upstream also has, and we did not, is `gpu_input_batch.py:434-440`, which widens the sentinel to `vocab_size` at admission. Because `max_num_logprobs` is fed from that map, upstream's `-1` arm is UNREACHABLE on the V1 path: defensive code for a value the input batch cannot produce. A user request gets the ordinary gathered shape and every consumer downstream reads one shape. We preserved the sentinel instead. That was deliberate and written down in `input_batch.h` as a recorded deviation, reasoning that our Sampler consumes `-1` directly. It does. What the note missed is that consuming it yields a DIFFERENT tensor shape, and that upstream can only afford that arm because nothing reaches it. We took the branch without the widening that makes it dead. So this mirrors the widening rather than teaching a second consumer about a second shape. `max_num_logprobs()` loses its sentinel special case and becomes the plain max upstream's `max(...)` already was: "all" is simply the largest count and wins on its own. The sampler's arm stays -- upstream keeps it, and a hand-built SamplingMetadata can still reach it -- but the comment now records both that the input batch cannot reach it and that its shape differs, which is the fact whose absence caused this. The existing case `C7 wiring: -1 logprobs sentinel dominates max_num_logprobs` asserted the deviation, so it is REPLACED rather than relaxed: its assertion was precisely the behaviour that crashes. The reason sits in the test beside it. Red first: SIGSEGV in UpdateSampleLogprobs for the engine case, `-1 == 1024` for the admission cases. Green after: `test_llm_engine` 13/13 (228 assertions), `test_input_batch` 26/26 (190), clean CPU Release build with zero warnings under -Werror, full ctest 360/360 in 729s -- no flake, no serial re-run needed. Issue #231's first diagnosis (mine) claimed upstream has no such branch. It does. Corrected in a comment on the issue rather than quietly, because the fix reads differently once the premise is right. Row `SAMPLE-LOGPROBS` stays DONE: this removes a deviation, it does not move a lifecycle. The roadmap issue table is deliberately untouched -- PR #235 already registers #231 there and duplicating the row would guarantee a keyed-record conflict between two open PRs of mine. Row: row/SAMPLE-LOGPROBS-ALL FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
1 parent c992456 commit fd9af7d

9 files changed

Lines changed: 319 additions & 17 deletions

File tree

.agents/coordination.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,19 @@ with the seed_oss-before-step3p5 ordering, re-verified. Scope:
15021502
`{include,src}/vllm/entrypoints/openai/tool_parsers/{qwen3_coder,kimi_k2,
15031503
glm47,minimax_m2,gemma4,seed_oss}.*`, structural_tags.*, utils.*, detect
15041504
table, tests, CMake. Details in the state-log entry of the same date.
1505+
**`logprobs=-1` engine crash (`SAMPLE-LOGPROBS`, 2026-08-09, issue #231).** Claude
1506+
Code (claude-opus-5), isolated worktree `/home/mudler/_git/vllm.cpp-logprobs-all`,
1507+
branch `row/SAMPLE-LOGPROBS-ALL`, base `origin/main` `58f43f66` pinned at worktree
1508+
creation. Spec [logprobs-all-sentinel.md](specs/logprobs-all-sentinel.md). NOT in
1509+
the claims table below: that table keys `SPIKE`/`ACTIVE` rows, and this is a
1510+
bugfix to a `DONE` row with no lifecycle move. Scope: the `num_logprobs` widening
1511+
+ `max_num_logprobs()` in `input_batch.{h,cpp}`, the unreachability comment on the
1512+
sampler's raw-vocab arm, two cases in `tests/vllm/v1/test_llm_engine.cpp`, the
1513+
replaced + added cases in `tests/vllm/v1/worker/test_input_batch.cpp`, and the
1514+
row's evidence cell. CPU-only; NO kernel, vt op, ABI, CMake, model file, GPU or
1515+
public-doc change. Does NOT touch the roadmap issue table — PR #235 already
1516+
registers #231 there and duplicating the row would guarantee a keyed-record
1517+
conflict.
15051518

15061519
| Claim | Row IDs | Agent | Worktree / remote dir | Branch | Owned scope | State | Last update |
15071520
|---|---|---|---|---|---|---|---|

.agents/engine-matrix.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ lifecycle are unchanged.
128128
|---|---|---|---|---|---|---|---|---|
129129
| `SAMPLE-CORE` | Ordered temperature, top-k/p, min-p, penalties, seed, stop, length, output-kind pipeline | T0 | `vllm/v1/sample/sampler.py:20,72,243`; `vllm/sampling_params.py:264,500`; `vllm/v1/worker/gpu_input_batch.py:889-963`; `tests/v1/sample/test_sampling_params_e2e.py:17,25,40,176` | `src/vllm/sampling_params.cpp:25,35,167`; `src/vllm/v1/sample/sampler.cpp:152,215`; `src/vllm/v1/worker/gpu/input_batch.cpp:255,344,451`; `src/vllm/v1/core/sched/utils.cpp:12` | `tests/vllm/test_sampling_params.cpp:13,64,231,265`; `tests/vllm/v1/sample/test_sampler.cpp:46,78,118,142,165,253`; `tests/vllm/v1/worker/test_input_batch.cpp` (C7 wiring: min_p/min_tokens/logprobs-count reach SamplingMetadata + condense/swap); `tests/vllm/v1/test_input_processor.cpp` (all_stop_token_ids) | [sampling-controls-c7.md](specs/sampling-controls-c7.md) (`SAMPLE-CORE`) | `ANCHOR-BACKFILL` | `CLAIM-ROADMAP-C7` |
130130
| `SAMPLE-PHILOX` | Torch-Philox bit-exact stochastic parity | T1 | `vllm/v1/sample/ops/topk_topp_sampler.py:70`; `vllm/v1/sample/sampler.py:243` | - | - | `planned: specs/philox-rng-parity.md` | `INVENTORIED` | - |
131-
| `SAMPLE-LOGPROBS` | Token logprobs payload end to end | T1 | `vllm/logprobs.py:12,157,175`; `vllm/v1/engine/logprobs.py:29,69,348`; `vllm/v1/outputs.py:28,38`; `vllm/v1/core/sched/scheduler.py:1815-1836`; `vllm/entrypoints/openai/completion/serving.py:652`; `vllm/entrypoints/openai/chat_completion/serving.py:1114,1141`; `tests/v1/sample/test_logprobs.py:303` | `include/vllm/logprobs.h` (Logprob/LogprobsOnePosition/SampleLogprobs + AppendLogprobsForNextPosition); `include/vllm/v1/engine/logprobs.h`+`src/vllm/v1/engine/logprobs.cpp` (LogprobsProcessor); `src/vllm/v1/outputs.cpp:24` (LogprobsTensors::slice_request); `src/vllm/v1/worker/gpu/runner.cpp:1272` (ModelRunnerOutput.logprobs); `src/vllm/v1/core/sched/scheduler.cpp:674` (slice -> EngineCoreOutput.new_logprobs); `src/vllm/v1/engine/output_processor.cpp` (LogprobsProcessor integration -> CompletionOutput.logprobs); `src/vllm/entrypoints/openai/serving_utils.cpp` (BuildCompletionLogProbs/BuildChatLogprobs); `src/vllm/entrypoints/openai/protocol.cpp` (CompletionLogProbs/ChatCompletionLogProbs to_json); `src/vllm/entrypoints/openai/serving_{completion,chat}.cpp` | `tests/vllm/entrypoints/openai/test_logprobs.cpp:67,103` (serialization vs vLLM oracle, RED-first N vs N+1; LogprobsProcessor accumulation + inertness `:137,167`); `tests/vllm/entrypoints/openai/test_serving.cpp:411,668` (e2e through the CPU engine: logprobs=K + chat top_logprobs, inert-when-off `:467`). Closing record [parity-ledger.md#L741](parity-ledger.md#L741) 2026-07-27 W5 row | [sampling-controls-c7.md](specs/sampling-controls-c7.md) (W5) | `DONE` | `ecda3ce1` |
131+
| `SAMPLE-LOGPROBS` | Token logprobs payload end to end | T1 | `vllm/logprobs.py:12,157,175`; `vllm/v1/engine/logprobs.py:29,69,348`; `vllm/v1/outputs.py:28,38`; `vllm/v1/core/sched/scheduler.py:1815-1836`; `vllm/entrypoints/openai/completion/serving.py:652`; `vllm/entrypoints/openai/chat_completion/serving.py:1114,1141`; `tests/v1/sample/test_logprobs.py:303` | `include/vllm/logprobs.h` (Logprob/LogprobsOnePosition/SampleLogprobs + AppendLogprobsForNextPosition); `include/vllm/v1/engine/logprobs.h`+`src/vllm/v1/engine/logprobs.cpp` (LogprobsProcessor); `src/vllm/v1/outputs.cpp:24` (LogprobsTensors::slice_request); `src/vllm/v1/worker/gpu/runner.cpp:1272` (ModelRunnerOutput.logprobs); `src/vllm/v1/core/sched/scheduler.cpp:674` (slice -> EngineCoreOutput.new_logprobs); `src/vllm/v1/engine/output_processor.cpp` (LogprobsProcessor integration -> CompletionOutput.logprobs); `src/vllm/entrypoints/openai/serving_utils.cpp` (BuildCompletionLogProbs/BuildChatLogprobs); `src/vllm/entrypoints/openai/protocol.cpp` (CompletionLogProbs/ChatCompletionLogProbs to_json); `src/vllm/entrypoints/openai/serving_{completion,chat}.cpp` | `tests/vllm/entrypoints/openai/test_logprobs.cpp:67,103` (serialization vs vLLM oracle, RED-first N vs N+1; LogprobsProcessor accumulation + inertness `:137,167`); `tests/vllm/entrypoints/openai/test_serving.cpp:411,668` (e2e through the CPU engine: logprobs=K + chat top_logprobs, inert-when-off `:467`). Closing record [parity-ledger.md#L741](parity-ledger.md#L741) 2026-07-27 W5 row. **#231 (2026-08-09): `logprobs=-1` crashed the engine** — the sentinel was preserved instead of widened, routing live requests into the sampler's raw-vocab arm (`sampler.py:122-125`), whose empty ids/ranks `UpdateSampleLogprobs` indexes anyway. Fixed by mirroring `gpu_input_batch.py:434-440` (widen to `vocab_size` at admission); the recorded `-1` deviation is GONE. `tests/vllm/v1/test_llm_engine.cpp` (full-vocab dict e2e, RED = SIGSEGV; finite-k no-regression) + `tests/vllm/v1/worker/test_input_batch.cpp` (widening at admission, paired and alone) — [logprobs-all-sentinel.md](specs/logprobs-all-sentinel.md) | [sampling-controls-c7.md](specs/sampling-controls-c7.md) (W5) | `DONE` | `ecda3ce1` |
132132
| `SAMPLE-PROMPT-LOGPROBS` | Prompt logprobs (payload plumbing + serialization done; runner prompt-position logits SOURCE pending) | T1 | `vllm/v1/engine/logprobs.py:121`; `vllm/v1/worker/gpu_model_runner.py:3842` (`_get_prompt_logprobs_dict`); `vllm/sampling_params.py:303` | payload path landed: `src/vllm/v1/engine/logprobs.cpp:75,100` (`UpdatePromptLogprobs`/`pop_prompt_logprobs`); `include/vllm/v1/engine/types.h:129,161` (`ModelRunnerOutput.prompt_logprobs_dict`, `EngineCoreOutput.new_prompt_logprobs_tensors`); `src/vllm/v1/core/sched/scheduler.cpp:688` (prompt_logprobs_dict slice); `src/vllm/v1/engine/output_processor.cpp:224` (`RequestOutput.prompt_logprobs`). **RESIDUAL: the runner does not yet compute prompt-position logits (lm_head over prompt tokens) — the tensor SOURCE; a runner/prefill addition adjacent to C5** | `tests/vllm/entrypoints/openai/test_logprobs.cpp:137,167` (LogprobsProcessor accumulation/inertness cover the shared consume path; the prompt-only source is inert until the runner lands) | `planned: specs/prompt-logprobs.md` | `PARTIAL` | - |
133133
| `SAMPLE-LOGPROB-TOKEN-IDS` | `logprob_token_ids` generative scoring and `logprobs_mode` variants beyond raw/processed (`SAMPLE-LOGPROBS` covers the payload only); carried from porting-inventory §6 (T1) at the v1 fold | T1 | `vllm/sampling_params.py:278,727`; `vllm/config/model.py:82,221` | - | - | `planned: specs/logprob-token-ids.md` | `INVENTORIED` | - |
134134
| `SAMPLE-LOGIT-FILTERS` | Logit bias, allowed-token IDs, bad words | T1 | `vllm/sampling_params.py:318,321,337,341,388-413,659-698`; `vllm/v1/sample/sampler.py:396`; `vllm/v1/worker/gpu_input_batch.py:446-471`; `vllm/entrypoints/openai/completion/protocol.py:369-371`; `tests/v1/sample/test_sampler.py:367,413`; `tests/v1/sample/test_sampling_params_e2e.py:106,147` | `include/vllm/sampling_params.h`+`src/vllm/sampling_params.cpp` (fields+validation); `src/vllm/entrypoints/openai/protocol.cpp` (`ParseLogitFilters`/`ApplyLogitFilters` clamp); `src/vllm/v1/worker/gpu/input_batch.cpp:255,344` (per-slot wiring+condense/swap); `src/vllm/v1/engine/input_processor.cpp` (bad_words tokenization); `src/vllm/v1/sample/sampler.cpp:239`; `src/vllm/v1/sample/logits_processor/builtin.cpp:41`; `src/vllm/v1/sample/ops/bad_words.cpp:13,55` | `tests/vllm/v1/sample/test_logits_processors.cpp:121,163,200`; `tests/vllm/test_sampling_params.cpp` (bad_words/allowed_token_ids validation); `tests/vllm/entrypoints/openai/test_protocol.cpp` (logit_bias clamp+parse); `tests/vllm/v1/worker/test_input_batch.cpp` (wiring, RED-first); `tests/vllm/v1/test_input_processor.cpp` (bad_words tokenization) | [sampling-controls-c7.md](specs/sampling-controls-c7.md) (`SAMPLE-LOGIT-FILTERS`) | `ANCHOR-BACKFILL` | `CLAIM-ROADMAP-C7` |
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# `SAMPLE-LOGPROBS``logprobs=-1` widens at admission
2+
3+
*(Live spec, 2026-08-09. Base `origin/main` `58f43f66`. Pin vLLM 0.26.0.dev0
4+
`555967922`. Issue [#231](https://github.com/mudler/vllm.cpp/issues/231). Row
5+
`SAMPLE-LOGPROBS` (`.agents/engine-matrix.md:131`, `DONE`) — a bugfix to a
6+
closed row, not a lifecycle move.)*
7+
8+
## Scope
9+
10+
`logprobs=-1` ("give me every vocab entry") crashes the engine. Mirror upstream's
11+
handling — widen the sentinel to `vocab_size` at admission — so one gathered
12+
shape reaches every consumer and the sampler's raw-vocab branch becomes as
13+
unreachable here as it is upstream.
14+
15+
In scope: `InputBatch::add_request`, `InputBatch::max_num_logprobs`, the comment
16+
on the sampler branch that stays, and the tests. Out of scope: `logprobs_mode`
17+
variants and `logprob_token_ids` generative scoring (`SAMPLE-LOGPROB-TOKEN-IDS`,
18+
`INVENTORIED`); the OpenAI `logprobs` request field, whose valid range is 0..5
19+
and never carries `-1` — this is the library/`SamplingParams` surface.
20+
21+
## Upstream chain
22+
23+
- `vllm/v1/worker/gpu_input_batch.py:434-440` — the widening being ported:
24+
`self.num_logprobs[req_id] = self.vocab_size if sampling_params.logprobs == -1
25+
else sampling_params.logprobs`.
26+
- `vllm/v1/sample/sampler.py:120-131` — the three-way branch, including the
27+
`num_logprobs == -1` raw-vocab arm. Reachable only from a hand-built
28+
`SamplingMetadata`, because `max_num_logprobs` is fed from the widened map.
29+
- `vllm/sampling_params.py:588-592``-1` is a legal value, validated.
30+
- `vllm/v1/engine/logprobs.py:69-119` — the consumer, which reads the gathered
31+
three-array shape unconditionally.
32+
33+
## Our baseline
34+
35+
The port is faithful at the sampler (`src/vllm/v1/sample/sampler.cpp:343-352`
36+
matches `sampler.py:122-125` arm for arm). The divergence is one layer up:
37+
`src/vllm/v1/worker/gpu/input_batch.cpp:292-297` deliberately PRESERVED the
38+
sentinel, and `max_num_logprobs()` at `:481-497` propagated it, both recorded as
39+
an intentional deviation in `input_batch.h`. That routes live requests into the
40+
branch upstream cannot reach.
41+
42+
`src/vllm/v1/engine/logprobs.cpp:51-73` then indexes `logprob_token_ids` and
43+
`selected_token_ranks`, which that shape leaves empty. Its guard is `width <= 0`;
44+
the raw-vocab shape sets `num_tokens_per_position = vocab`, so the guard passes
45+
and the reads run off the end of two empty vectors.
46+
47+
## Port map
48+
49+
| Upstream (`555967922`) | Local anchor |
50+
|---|---|
51+
| `gpu_input_batch.py:434-440` (widen `-1``vocab_size`) | `InputBatch::add_request`, `src/vllm/v1/worker/gpu/input_batch.cpp` |
52+
| `gpu_input_batch.py:1150-1151` (`max(num_logprobs.values())`) | `InputBatch::max_num_logprobs`, same file — plain max once every value is concrete |
53+
| `sampler.py:122-125` (the raw-vocab arm, unreachable on the V1 path) | `src/vllm/v1/sample/sampler.cpp` — kept, and its unreachability + differing shape now stated where a future reader will meet it |
54+
55+
## Design
56+
57+
One line at admission. `num_logprobs[req_id] = *sp.logprobs == -1 ? vocab_size :
58+
*sp.logprobs`, exactly as upstream. `max_num_logprobs()` loses its sentinel
59+
special case and becomes the plain max upstream's `max(...)` already was: a
60+
request asking for "all" now carries the largest possible count and wins that max
61+
on its own.
62+
63+
Nothing downstream changes. `GatherLogprobs` with `k == vocab` produces the
64+
ordinary `[n, vocab+1]` shape; `AppendLogprobsForNextPosition` already handles
65+
`num_logprobs == -1` on the *engine* side by deriving `k` from the row width
66+
(`logprobs.h:82`), so the `LogprobsProcessor` reads it correctly without change.
67+
68+
**Why not teach the consumer the second shape.** It is the other available fix
69+
and it is worse: it keeps a deviation whose only effect is to make our engine
70+
carry two logprob shapes where upstream carries one, and every future consumer
71+
would have to know that. Removing the deviation deletes the class of bug.
72+
73+
## Tests to port
74+
75+
Upstream has no test for this (the value cannot reach the branch there), so these
76+
are written, not ported, and recorded as such.
77+
78+
1. `tests/vllm/v1/test_llm_engine.cpp` — a `logprobs=-1` request through the
79+
engine returns one entry per generated token, each carrying every vocab id
80+
exactly once, the sampled token at rank 1, and a row that exponentiates to
81+
1.0. **RED: SIGSEGV** inside `UpdateSampleLogprobs`.
82+
2. Same file — a finite `logprobs=2` request still returns at most `k+1` entries,
83+
guarding the ordinary path against a regression in the same edit.
84+
3. `tests/vllm/v1/worker/test_input_batch.cpp``-1` is widened at admission
85+
(the map holds `vocab_size`, never the sentinel), both alongside a finite
86+
request and alone.
87+
88+
The existing case `C7 wiring: -1 logprobs sentinel dominates max_num_logprobs`
89+
asserted the deviation, so it is REPLACED, not relaxed: its assertion
90+
(`max_num_logprobs == -1`) is exactly the behaviour that crashes, and the
91+
replacement asserts the mirrored value with the reason written beside it.
92+
93+
## Gates
94+
95+
CPU reference backend.
96+
97+
```sh
98+
cmake -S . -B build-cpu -G Ninja -DCMAKE_BUILD_TYPE=Release \
99+
-DVLLM_CPP_CUDA=OFF -DVLLM_CPP_VULKAN=OFF -DVLLM_CPP_METAL=OFF
100+
cmake --build build-cpu -j 18
101+
./build-cpu/tests/test_llm_engine
102+
./build-cpu/tests/test_input_batch
103+
ctest --test-dir build-cpu -j 6 --output-on-failure
104+
```
105+
106+
A failure under `-j` is re-run serially before it is called a regression.
107+
108+
## Dependencies
109+
110+
None. No kernel, no vt op, no ABI, no model file, no GPU. Independent of
111+
`SAMPLE-PROMPT-LOGPROBS` (#223): that row's `-1` path was already correct,
112+
because the runner widens to `vocab_size` for prompt logprobs the way this
113+
change now does for sampled ones.
114+
115+
## Work breakdown
116+
117+
Single change. There is no W2.
118+
119+
## Risks/decisions
120+
121+
1. **Replacing an existing assertion.** Mitigated by stating in the test itself
122+
why the old one encoded the defect, and by the engine-level RED that shows
123+
what the old behaviour actually did.
124+
2. **A hand-built `SamplingMetadata` can still reach the raw-vocab branch.** True
125+
upstream too. The branch stays (mirroring), and the comment now says the shape
126+
differs so a new consumer branches instead of indexing blindly.
127+
3. **`vocab_size` columns is a large allocation.** `GatherLogprobs` at
128+
`k == vocab` does a full sort per row. That is what "all logprobs" costs, and
129+
what upstream costs; no speed claim is made or owed.
130+
131+
## Evidence
132+
133+
In the PR body: the RED crash, the GREEN runs, the full `ctest` summary.
134+
135+
## Stop conditions
136+
137+
- If the `-1` sentinel turns out to be load-bearing anywhere else, stop and
138+
re-spec rather than widening the fix.
139+
- Never make the consumer's `width <= 0` guard broader to swallow the shape —
140+
that hides the defect instead of removing it.
141+
142+
## Outcome
143+
144+
*(2026-08-09. Row stays `DONE`; the fix removes a recorded deviation.)*
145+
146+
**What the bug actually was.** Not a bad port. `sampler.cpp:343-352` matches
147+
`sampler.py:122-125` arm for arm, and reading only those two files makes the
148+
crash look like the sampler's fault. The defect was a DELIBERATE choice one layer
149+
up — preserving the `-1` sentinel instead of widening it — written down in
150+
`input_batch.h` as an intentional deviation, with the reasoning "our Sampler
151+
reads it directly". That was true. What it missed is that the branch it routes
152+
into produces a DIFFERENT shape (empty ids and ranks), and upstream can only
153+
afford that branch because its own input batch can never reach it. We adopted the
154+
branch without adopting the widening that makes it dead.
155+
156+
The first version of issue #231 said upstream has no such branch. That was wrong
157+
and is corrected in a comment on the issue rather than silently: the branch
158+
exists at the pin, it is simply unreachable there.
159+
160+
**Measured.** RED: `SIGSEGV` inside `UpdateSampleLogprobs` for the engine case,
161+
and `-1 == 1024` for the admission cases. GREEN: `test_llm_engine` 13/13 (228
162+
assertions), `test_input_batch` 26/26 (190), clean CPU Release build with zero
163+
warnings under `-Werror`, full `ctest` **360/360** (729 s, no flake, no serial
164+
re-run needed).
165+
166+
**Rejected: teaching `UpdateSampleLogprobs` the raw-vocab shape.** It fixes the
167+
crash and keeps the deviation, so our engine would carry two logprob shapes where
168+
upstream carries one, and every future consumer would need to know that. The
169+
widening deletes the class of bug instead of the instance.
170+
171+
**Kept deliberately:** the sampler's `-1` arm. Upstream keeps it, and a caller
172+
that hand-builds `SamplingMetadata` can still reach it, so the comment there now
173+
states both that it is unreachable from the input batch and that its shape
174+
differs — which is the fact whose absence caused this.
175+
176+
**Default.** No flag. `logprobs=-1` was already a validated, legal value; it now
177+
returns what it says.

docs/USAGE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,10 @@ auto engine = vllm::entrypoints::LoadedEngine::FromModelDir(model_dir, ep);
645645
The underlying portable tensor runtime is `vt::` ([`include/vt/`](../include/vt/)),
646646
which carries no ggml or PyTorch dependency.
647647

648+
`SamplingParams::logprobs` accepts `-1` for "every vocab entry", as vLLM's does;
649+
it returns the same gathered shape a finite count returns, one entry per vocab id
650+
per position. (Over HTTP the OpenAI `logprobs` field keeps its own 0..5 range.)
651+
648652
## Multimodal input (image, video, audio to text)
649653

650654
Multimodal input is served over the **OpenAI API**, not the CLI. `vllm-cli` is text-only:

include/vllm/v1/worker/gpu/input_batch.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,12 @@ class InputBatch {
292292
// `custom_logit_processor`). Only present for a request that registered one;
293293
// make_sampling_metadata emits it as SamplingMetadata.logits_processors.
294294
std::map<int, LogitsProcessorCallback> logits_processors;
295-
// req_id -> requested sample-logprob count (gpu_input_batch.py:435-440); the
296-
// -1 "all" sentinel is preserved (our Sampler consumes it directly, whereas
297-
// upstream stores vocab_size — recorded deviation).
295+
// req_id -> requested sample-logprob count (gpu_input_batch.py:434-440). The
296+
// `-1` ("all logprobs") sentinel is WIDENED to vocab_size at add_request, as
297+
// upstream does, so every value here is a concrete count and one gathered
298+
// shape reaches every consumer. Preserving the sentinel instead was a
299+
// recorded deviation until it turned out to crash the engine
300+
// (specs/logprobs-all-sentinel.md).
298301
std::map<std::string, int> num_logprobs;
299302
// Lazily-allocated [max_num_reqs][vocab_size] EXCLUDE mask (TRUE == mask this
300303
// token to -inf). Empty until the first request with allowed_token_ids; a row

0 commit comments

Comments
 (0)