Skip to content

Commit bc02352

Browse files
wojciech-waisfede-kamelaoshen02
committed
[Bugfix][V1/V2] Fix prompt_logprobs to respect logprobs_mode
prompt_logprobs ignored the logprobs_mode config — all modes returned log_softmax results. This fix threads the mode through both V1 and V2 GPU model runners so that *_logits modes return raw logits and *_logprobs modes return log_softmax values. V1 runner (gpu_model_runner.py): inline mode branch before gather. V2 runner (prompt_logprob.py): route to compute_topk_logits or compute_topk_logprobs based on mode. Config (vllm.py): remove V2 runner unsupported-feature gate for raw_logits/processed_logits now that V2 handles them. Fixes: #35832 Co-authored-by: Federico Kamelhar <209537060+fede-kamel@users.noreply.github.com> Co-authored-by: Allen Shen <aoshen@inferact.ai> Signed-off-by: Allen Shen <aoshen@inferact.ai>
1 parent 34b560b commit bc02352

7 files changed

Lines changed: 120 additions & 25 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
"""Unit tests for prompt-side logprobs_mode branching.
4+
5+
The mode selects logits (float32) vs log_softmax(logits) for prompt
6+
scoring. These tests verify the branch without a GPU model runner.
7+
"""
8+
9+
import pytest
10+
import torch
11+
12+
13+
@pytest.mark.parametrize(
14+
"mode",
15+
["raw_logits", "processed_logits", "raw_logprobs", "processed_logprobs"],
16+
)
17+
def test_prompt_score_mode_branch(mode: str):
18+
logits = torch.tensor([[1.0, 2.0, 3.0]], dtype=torch.float16)
19+
compute_logprobs_called = False
20+
21+
def compute_logprobs(x: torch.Tensor) -> torch.Tensor:
22+
nonlocal compute_logprobs_called
23+
compute_logprobs_called = True
24+
return x.log_softmax(dim=-1, dtype=torch.float32)
25+
26+
if mode.endswith("logits"):
27+
scores = logits.to(torch.float32)
28+
else:
29+
scores = compute_logprobs(logits)
30+
31+
assert scores.dtype == torch.float32
32+
33+
if mode.endswith("logits"):
34+
torch.testing.assert_close(scores, logits.to(torch.float32))
35+
assert not compute_logprobs_called
36+
else:
37+
expected = logits.log_softmax(dim=-1, dtype=torch.float32)
38+
torch.testing.assert_close(scores, expected)
39+
assert compute_logprobs_called

tests/v1/sample/test_sampling_params_e2e.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,25 @@ def test_seed(llm):
182182

183183
assert out_1[0].outputs[0].text == out_2[0].outputs[0].text
184184
assert out_1[0].outputs[0].text != out_3[0].outputs[0].text
185+
186+
187+
def test_prompt_logprobs_respects_logprobs_mode():
188+
"""prompt_logprobs values must differ between raw_logits and raw_logprobs."""
189+
prompt = "Hello world"
190+
values: dict[str, float] = {}
191+
192+
for mode in ("raw_logits", "raw_logprobs"):
193+
engine = LLM(MODEL, enforce_eager=True, logprobs_mode=mode)
194+
out = engine.generate(
195+
prompt,
196+
SamplingParams(max_tokens=1, prompt_logprobs=0, temperature=0),
197+
)[0]
198+
assert out.prompt_logprobs is not None
199+
assert out.prompt_logprobs[1] is not None
200+
tok_id = out.prompt_token_ids[1]
201+
values[mode] = out.prompt_logprobs[1][tok_id].logprob
202+
del engine
203+
204+
assert values["raw_logits"] != values["raw_logprobs"], (
205+
"prompt_logprobs should differ between logits and logprobs modes"
206+
)

vllm/config/vllm.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,13 +2103,6 @@ def _get_v2_model_runner_unsupported_features(self) -> list[str]:
21032103
if model_config is not None and model_config.enable_prompt_embeds:
21042104
unsupported.append("prompt embeds")
21052105

2106-
if (
2107-
model_config is not None
2108-
and model_config.runner_type == "generate"
2109-
and model_config.logprobs_mode in ("raw_logits", "processed_logits")
2110-
):
2111-
unsupported.append(f"logprobs mode '{model_config.logprobs_mode}'")
2112-
21132106
if self.cache_config.kv_sharing_fast_prefill:
21142107
# Will be added by https://github.com/vllm-project/vllm/pull/35045
21152108
unsupported.append("KV sharing fast prefill")

vllm/v1/worker/gpu/model_runner.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,10 @@ def load_model(self, load_dummy_weights: bool = False, *args, **kwargs) -> None:
341341
self.speculative_config,
342342
self.device,
343343
)
344-
self.prompt_logprobs_worker = PromptLogprobsWorker(self.max_num_reqs)
344+
self.prompt_logprobs_worker = PromptLogprobsWorker(
345+
self.max_num_reqs,
346+
logprobs_mode=self.model_config.logprobs_mode,
347+
)
345348
self.structured_outputs_worker = StructuredOutputsWorker(
346349
max_num_logits=self.max_num_reqs * self.decode_query_len,
347350
vocab_size=self.vocab_size,

vllm/v1/worker/gpu/sample/logprob.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,34 @@ def compute_token_logprobs(
106106
return logprobs
107107

108108

109+
def compute_topk_logits(
110+
logits: torch.Tensor,
111+
num_logprobs: int,
112+
sampled_token_ids: torch.Tensor,
113+
) -> LogprobsTensors:
114+
"""Like compute_topk_logprobs but returns raw logits (no log_softmax)."""
115+
batch_size, vocab_size = logits.shape
116+
logits_f32 = logits.to(torch.float32)
117+
118+
logprob_token_ids = sampled_token_ids.unsqueeze(-1)
119+
if num_logprobs > 0:
120+
topk_indices = torch.topk(logits_f32, num_logprobs, dim=-1).indices
121+
logprob_token_ids = torch.cat(
122+
(logprob_token_ids, topk_indices), dim=1
123+
)
124+
125+
values = logits_f32.gather(-1, logprob_token_ids)
126+
127+
target_vals = logits_f32.gather(-1, sampled_token_ids.unsqueeze(-1))
128+
token_ranks = (logits_f32 >= target_vals).sum(-1)
129+
130+
return LogprobsTensors(
131+
logprob_token_ids=logprob_token_ids,
132+
logprobs=values,
133+
selected_token_ranks=token_ranks,
134+
)
135+
136+
109137
def compute_topk_logprobs(
110138
logits: torch.Tensor,
111139
num_logprobs: int,

vllm/v1/worker/gpu/sample/prompt_logprob.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
from vllm.triton_utils import tl, triton
1010
from vllm.v1.outputs import LogprobsTensors
1111
from vllm.v1.worker.gpu.input_batch import InputBatch
12-
from vllm.v1.worker.gpu.sample.logprob import compute_topk_logprobs
12+
from vllm.v1.worker.gpu.sample.logprob import (
13+
compute_topk_logits,
14+
compute_topk_logprobs,
15+
)
1316

1417

1518
class PromptLogprobsWorker:
16-
def __init__(self, max_num_reqs: int):
19+
def __init__(self, max_num_reqs: int, logprobs_mode: str = "raw_logprobs"):
1720
self.max_num_reqs = max_num_reqs
21+
self.logprobs_mode = logprobs_mode
1822

1923
self.uses_prompt_logprobs = np.zeros(self.max_num_reqs, dtype=bool)
2024
self.num_prompt_logprobs = np.zeros(self.max_num_reqs, dtype=np.int32)
@@ -82,6 +86,7 @@ def compute_prompt_logprobs(
8286
hidden_states[: input_batch.num_tokens],
8387
logits_fn,
8488
max_num_prompt_logprobs,
89+
self.logprobs_mode,
8590
)
8691
)
8792

@@ -206,33 +211,36 @@ def compute_prompt_logprobs_with_chunking(
206211
prompt_hidden_states: torch.Tensor,
207212
logits_fn: Callable[[torch.Tensor], torch.Tensor],
208213
num_prompt_logprobs: int,
214+
logprobs_mode: str = "raw_logprobs",
209215
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
210216
# Since materializing the full prompt logits can take too much memory,
211217
# we compute it in chunks.
212218
CHUNK_SIZE = 1024
213219
token_ids = []
214-
logprobs = []
220+
scores = []
215221
ranks = []
216222
prompt_token_ids = prompt_token_ids.to(torch.int64)
217223
for start_idx in range(0, prompt_token_ids.shape[0], CHUNK_SIZE):
218224
end_idx = start_idx + CHUNK_SIZE
219-
# NOTE(woosuk): logits_fn can be slow because it involves all-gather.
220225
prompt_logits = logits_fn(prompt_hidden_states[start_idx:end_idx])
221-
requested_num_prompt_logprobs = (
226+
requested_num = (
222227
prompt_logits.shape[-1]
223228
if num_prompt_logprobs == -1
224229
else num_prompt_logprobs
225230
)
226-
prompt_logprobs = compute_topk_logprobs(
227-
prompt_logits,
228-
requested_num_prompt_logprobs,
229-
prompt_token_ids[start_idx:end_idx],
231+
compute_fn = (
232+
compute_topk_logits if logprobs_mode.endswith("logits") else compute_topk_logprobs
230233
)
231-
token_ids.append(prompt_logprobs.logprob_token_ids)
232-
logprobs.append(prompt_logprobs.logprobs)
233-
ranks.append(prompt_logprobs.selected_token_ranks)
234+
result = compute_fn(
235+
prompt_logits,
236+
requested_num,
237+
prompt_token_ids[start_idx:end_idx]
238+
)
239+
token_ids.append(result.logprob_token_ids)
240+
scores.append(result.logprobs)
241+
ranks.append(result.selected_token_ranks)
234242

235243
token_ids = torch.cat(token_ids, dim=0) if len(token_ids) > 1 else token_ids[0]
236-
logprobs = torch.cat(logprobs, dim=0) if len(logprobs) > 1 else logprobs[0]
244+
scores = torch.cat(scores, dim=0) if len(scores) > 1 else scores[0]
237245
ranks = torch.cat(ranks, dim=0) if len(ranks) > 1 else ranks[0]
238-
return token_ids, logprobs, ranks
246+
return token_ids, scores, ranks

vllm/v1/worker/gpu_model_runner.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5556,10 +5556,12 @@ def _get_prompt_logprobs_dict(
55565556
# to gather the logprob for.
55575557
tgt_token_ids = prompt_token_ids[start_tok : start_tok + num_logits]
55585558

5559-
# Compute prompt logprobs.
5560-
logprobs = self.sampler.compute_logprobs(logits)
5559+
if self.model_config.logprobs_mode.endswith("logits"):
5560+
scores = logits.to(torch.float32)
5561+
else:
5562+
scores = self.sampler.compute_logprobs(logits)
55615563
token_ids, logprobs, ranks, _ = self.sampler.gather_logprobs(
5562-
logprobs, num_prompt_logprobs, tgt_token_ids
5564+
scores, num_prompt_logprobs, tgt_token_ids
55635565
)
55645566

55655567
# Transfer GPU->CPU async.

0 commit comments

Comments
 (0)