[Frontend] Expose logprob_token_ids on Python OpenAI endpoints (#43463)
Signed-off-by: Lang Zhao <lang.zhao@galileo.ai> Co-authored-by: Claude <noreply@anthropic.com>
langzhao-netizen committed
Jul 13, 2026 at 14:40 UTC
8b8af2caf739a7537b9ca848b836733c6533bf20
9 files changed
+401
-7
tests/entrypoints/openai/chat_completion/test_batched_chat_completions.py
+34
@@ -187,3 +187,37 @@ async def test_batched_chat_completions_return_tokens_as_token_ids(
187
content = data["choices"][0]["logprobs"]["content"]
188
assert content
189
assert all(entry["token"].startswith("token_id:") for entry in content)
190
+
191
+
192
+@pytest.mark.asyncio
193
+async def test_batched_chat_completions_logprob_token_ids(
194
+ server: RemoteOpenAIServer,
195
+) -> None:
196
+ conversations = [[{"role": "user", "content": "Hello"}]]
197
+
198
+ async with httpx.AsyncClient() as http_client:
199
+ response = await http_client.post(
200
+ f"{server.url_for('v1/chat/completions/batch')}",
201
+ json={
202
+ "model": MODEL_NAME,
203
+ "messages": conversations,
204
+ "max_tokens": 1,
205
+ "temperature": 0,
206
+ "logprobs": True,
207
+ "top_logprobs": 5,
208
+ "logprob_token_ids": [100, 1000, 5000],
209
+ "return_tokens_as_token_ids": True,
210
+ },
211
+ timeout=60,
212
+ )
213
+
214
+ assert response.status_code == 200, response.text
215
+ content = response.json()["choices"][0]["logprobs"]["content"]
216
+ assert content
217
+ sampled_token = content[0]["token"]
218
+ assert {entry["token"] for entry in content[0]["top_logprobs"]} == {
219
+ "token_id:100",
220
+ "token_id:1000",
221
+ "token_id:5000",
222
+ sampled_token,
223
+ }
tests/entrypoints/openai/chat_completion/test_logprob_token_ids.py
new
+208
@@ -0,0 +1,208 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""End-to-end tests for the `logprob_token_ids` field on the OpenAI-compat
4
+chat-completion endpoint.
5
+
6
+`logprob_token_ids` lets a caller pin the set of vocab ids whose logprobs
7
+should appear in the response, independent of where those ids would rank in
8
+the natural top-k distribution. This is the primitive that multilabel
9
+scoring postprocessors use to gather logprobs at a fixed small label
10
+vocabulary (e.g. PII detection where each label corresponds to a known
11
+digit-token vocab id).
12
+"""
13
+
14
+import math
15
+
16
+import pytest
17
+from pydantic import ValidationError
18
+
19
+from tests.utils import RemoteOpenAIServer
20
+from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
21
+from vllm.entrypoints.openai.completion.protocol import CompletionRequest
22
+
23
+MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct"
24
+
25
+
26
+@pytest.fixture(scope="module")
27
+def server():
28
+ args = [
29
+ "--max-model-len",
30
+ "1024",
31
+ "--max-num-seqs",
32
+ "8",
33
+ "--enforce-eager",
34
+ ]
35
+ with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
36
+ yield remote_server
37
+
38
+
39
+def _logprob_entries(resp) -> list:
40
+ """Return the top_logprobs list from the first generated position."""
41
+ return resp.choices[0].logprobs.content[0].top_logprobs
42
+
43
+
44
+def _token_id(token: str) -> int:
45
+ assert token.startswith("token_id:"), (
46
+ "expected return_tokens_as_token_ids=True to yield the "
47
+ f"`token_id:<int>` form, got {token!r}"
48
+ )
49
+ return int(token.removeprefix("token_id:"))
50
+
51
+
52
+def _top_logprob_token_ids(resp) -> list[int]:
53
+ return [_token_id(entry.token) for entry in _logprob_entries(resp)]
54
+
55
+
56
+def test_chat_request_decouples_top_k_from_explicit_token_ids():
57
+ request = ChatCompletionRequest(
58
+ model=MODEL_NAME,
59
+ messages=[{"role": "user", "content": "Hello"}],
60
+ logprobs=True,
61
+ top_logprobs=5,
62
+ logprob_token_ids=[5000],
63
+ )
64
+
65
+ sampling_params = request.to_sampling_params(
66
+ max_tokens=1, default_sampling_params={}
67
+ )
68
+
69
+ assert sampling_params.logprobs is None
70
+ assert sampling_params.logprob_token_ids == [5000]
71
+ assert sampling_params.num_logprobs == 1
72
+
73
+
74
+def test_completion_request_decouples_top_k_from_explicit_token_ids():
75
+ request = CompletionRequest(
76
+ model=MODEL_NAME,
77
+ prompt="Hello",
78
+ logprobs=5,
79
+ logprob_token_ids=[5000],
80
+ )
81
+
82
+ sampling_params = request.to_sampling_params(max_tokens=1)
83
+
84
+ assert sampling_params.logprobs is None
85
+ assert sampling_params.logprob_token_ids == [5000]
86
+ assert sampling_params.num_logprobs == 1
87
+
88
+
89
+def test_completion_rejects_explicit_token_ids_without_generated_tokens():
90
+ with pytest.raises(ValidationError, match="no output tokens are generated"):
91
+ CompletionRequest(
92
+ model=MODEL_NAME,
93
+ prompt="Hello",
94
+ echo=True,
95
+ max_tokens=0,
96
+ logprobs=5,
97
+ logprob_token_ids=[5000],
98
+ )
99
+
100
+
101
+def test_requests_reject_explicit_token_ids_with_beam_search():
102
+ with pytest.raises(ValidationError, match="not supported with beam search"):
103
+ ChatCompletionRequest(
104
+ model=MODEL_NAME,
105
+ messages=[{"role": "user", "content": "Hello"}],
106
+ logprobs=True,
107
+ logprob_token_ids=[5000],
108
+ use_beam_search=True,
109
+ )
110
+
111
+ with pytest.raises(ValidationError, match="not supported with beam search"):
112
+ CompletionRequest(
113
+ model=MODEL_NAME,
114
+ prompt="Hello",
115
+ logprobs=5,
116
+ logprob_token_ids=[5000],
117
+ use_beam_search=True,
118
+ )
119
+
120
+
121
+@pytest.mark.asyncio
122
+@pytest.mark.parametrize(
123
+ ("requested_ids", "top_logprobs", "sampled_id"),
124
+ [
125
+ pytest.param([5000], 5, 42, id="original-issue-shape"),
126
+ pytest.param([100, 1000, 5000], 3, 42, id="sampled-outside-set"),
127
+ pytest.param([100, 1000, 5000], 3, 5000, id="sampled-inside-set"),
128
+ ],
129
+)
130
+async def test_logprob_token_ids_returns_requested_ids(
131
+ server, requested_ids, top_logprobs, sampled_id
132
+):
133
+ async with server.get_async_client() as client:
134
+ resp = await client.chat.completions.create(
135
+ model=MODEL_NAME,
136
+ messages=[{"role": "user", "content": "Hello"}],
137
+ max_tokens=1,
138
+ temperature=0,
139
+ logprobs=True,
140
+ top_logprobs=top_logprobs,
141
+ extra_body={
142
+ "logprob_token_ids": requested_ids,
143
+ "allowed_token_ids": [sampled_id],
144
+ "return_tokens_as_token_ids": True,
145
+ },
146
+ )
147
+ entries = _logprob_entries(resp)
148
+ returned_token_ids = _top_logprob_token_ids(resp)
149
+
150
+ assert _token_id(resp.choices[0].logprobs.content[0].token) == sampled_id
151
+ assert set(returned_token_ids) == {*requested_ids, sampled_id}
152
+ assert len(returned_token_ids) == len(set(returned_token_ids))
153
+
154
+ for e in entries:
155
+ assert isinstance(e.logprob, float)
156
+ assert not math.isnan(e.logprob)
157
+ assert not math.isinf(e.logprob)
158
+
159
+
160
+@pytest.mark.asyncio
161
+async def test_logprob_token_ids_stream_with_no_top_k(server):
162
+ async with server.get_async_client() as client:
163
+ stream = await client.chat.completions.create(
164
+ model=MODEL_NAME,
165
+ messages=[{"role": "user", "content": "Hello"}],
166
+ max_tokens=1,
167
+ temperature=0,
168
+ logprobs=True,
169
+ top_logprobs=None,
170
+ stream=True,
171
+ extra_body={
172
+ "logprob_token_ids": [5000],
173
+ "allowed_token_ids": [42],
174
+ "return_tokens_as_token_ids": True,
175
+ },
176
+ )
177
+
178
+ returned_token_ids: list[int] = []
179
+ async for chunk in stream:
180
+ if not chunk.choices or chunk.choices[0].logprobs is None:
181
+ continue
182
+ for content in chunk.choices[0].logprobs.content:
183
+ returned_token_ids.extend(
184
+ _token_id(entry.token) for entry in content.top_logprobs
185
+ )
186
+
187
+ assert set(returned_token_ids) == {42, 5000}
188
+
189
+
190
+@pytest.mark.asyncio
191
+async def test_logprob_token_ids_default_behavior_unchanged(server):
192
+ """Without `logprob_token_ids`, the response carries the natural top-k
193
+ most-likely tokens. This guards against the new field accidentally
194
+ changing the default-path output."""
195
+ async with server.get_async_client() as client:
196
+ resp = await client.chat.completions.create(
197
+ model=MODEL_NAME,
198
+ messages=[{"role": "user", "content": "Hello"}],
199
+ max_tokens=1,
200
+ temperature=0,
201
+ logprobs=True,
202
+ top_logprobs=5,
203
+ )
204
+ entries = _logprob_entries(resp)
205
+ assert len(entries) == 5
206
+ # Default mode emits log_softmax, so all values are <= 0.
207
+ for e in entries:
208
+ assert e.logprob <= 0.0
tests/entrypoints/openai/completion/test_completion.py
+55
@@ -175,6 +175,61 @@ async def test_some_logprobs(client: openai.AsyncOpenAI, model_name: str):
175
assert 5 <= len(choice.logprobs.top_logprobs[0]) <= 6
176
177
178
+@pytest.mark.asyncio
179
+async def test_logprob_token_ids(client: openai.AsyncOpenAI):
180
+ completion = await client.completions.create(
181
+ model=MODEL_NAME,
182
+ prompt="Hello",
183
+ max_tokens=1,
184
+ temperature=0.0,
185
+ logprobs=5,
186
+ extra_body={
187
+ "logprob_token_ids": [5000],
188
+ "allowed_token_ids": [42],
189
+ "return_tokens_as_token_ids": True,
190
+ },
191
+ )
192
+
193
+ choice = completion.choices[0]
194
+ assert choice.logprobs is not None
195
+ assert choice.logprobs.tokens == ["token_id:42"]
196
+ assert choice.logprobs.top_logprobs is not None
197
+ assert set(choice.logprobs.top_logprobs[0]) == {
198
+ "token_id:42",
199
+ "token_id:5000",
200
+ }
201
+
202
+
203
+@pytest.mark.asyncio
204
+async def test_logprob_token_ids_stream(client: openai.AsyncOpenAI):
205
+ stream = await client.completions.create(
206
+ model=MODEL_NAME,
207
+ prompt="Hello",
208
+ max_tokens=1,
209
+ temperature=0.0,
210
+ logprobs=5,
211
+ stream=True,
212
+ extra_body={
213
+ "logprob_token_ids": [5000],
214
+ "allowed_token_ids": [42],
215
+ "return_tokens_as_token_ids": True,
216
+ },
217
+ )
218
+
219
+ returned_top_logprobs: list[dict[str, float]] = []
220
+ async for chunk in stream:
221
+ logprobs = chunk.choices[0].logprobs
222
+ if logprobs is not None and logprobs.top_logprobs is not None:
223
+ returned_top_logprobs.extend(
224
+ top_logprobs
225
+ for top_logprobs in logprobs.top_logprobs
226
+ if top_logprobs is not None
227
+ )
228
+
229
+ assert len(returned_top_logprobs) == 1
230
+ assert set(returned_top_logprobs[0]) == {"token_id:42", "token_id:5000"}
231
+
232
+
233
@pytest.mark.asyncio
234
@pytest.mark.parametrize(
235
"model_name",
vllm/entrypoints/openai/chat_completion/batch_serving.py
+4
-1
@@ -251,12 +251,15 @@ class OpenAIServingChatBatch(OpenAIServingChat):
251
for output in final_res.outputs:
252
self._raise_if_error(output.finish_reason, request_id)
253
254
- if request.logprobs and request.top_logprobs is not None:
254
+ if request.logprobs and (
255
+ request.top_logprobs is not None or request.logprob_token_ids
256
+ ):
257
assert output.logprobs is not None, "Did not output logprobs"
258
logprobs = self._create_chat_logprobs(
259
token_ids=output.token_ids,
260
top_logprobs=output.logprobs,
261
num_output_top_logprobs=request.top_logprobs,
262
+ logprob_token_ids=request.logprob_token_ids,
263
tokenizer=tokenizer,
264
return_as_token_id=request.return_tokens_as_token_ids,
265
)
vllm/entrypoints/openai/chat_completion/protocol.py
+42
-1
@@ -270,6 +270,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
270
),
271
)
272
prompt_logprobs: int | None = None
273
+ logprob_token_ids: list[int] | None = Field(
274
+ default=None,
275
+ description=(
276
+ "Specific vocab token IDs to return logprobs for at each generated "
277
+ "position, in addition to the sampled token. More efficient than "
278
+ "`top_logprobs=-1` when only a small fixed label set is needed "
279
+ "(e.g. multilabel scoring "
280
+ "where each label corresponds to a known vocab id). When set, "
281
+ "this explicit token selection takes precedence over the natural "
282
+ "top-k selected by `top_logprobs`. Requires `logprobs=True`."
283
+ ),
284
+ )
285
allowed_token_ids: list[int] | None = None
286
bad_words: list[str] = Field(default_factory=list)
287
# --8<-- [end:chat-completion-sampling-params]
@@ -690,8 +702,13 @@ class ChatCompletionRequest(OpenAIBaseModel):
702
seed=self.seed,
703
stop=self.stop,
704
stop_token_ids=stop_token_ids,
693
- logprobs=self.top_logprobs if self.logprobs else None,
705
+ logprobs=(
706
+ self.top_logprobs
707
+ if self.logprobs and not self.logprob_token_ids
708
+ else None
709
+ ),
710
prompt_logprobs=prompt_logprobs,
711
+ logprob_token_ids=self.logprob_token_ids or None,
712
ignore_eos=self.ignore_eos,
713
max_tokens=max_tokens,
714
min_tokens=self.min_tokens,
@@ -756,6 +773,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
773
@model_validator(mode="before")
774
@classmethod
775
def check_logprobs(cls, data):
776
+ if data.get("logprob_token_ids") and data.get("use_beam_search"):
777
+ raise VLLMValidationError(
778
+ "`logprob_token_ids` is not supported with beam search.",
779
+ parameter="logprob_token_ids",
780
+ )
781
+
782
+ if data.get("logprob_token_ids") and not data.get("logprobs"):
783
+ raise VLLMValidationError(
784
+ "when using `logprob_token_ids`, `logprobs` must be set to true.",
785
+ parameter="logprob_token_ids",
786
+ )
787
+
788
if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
789
if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
790
raise VLLMValidationError(
@@ -1009,6 +1038,14 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
1038
logit_bias: dict[str, float] | None = None
1039
logprobs: bool | None = False
1040
top_logprobs: int | None = 0
1041
+ logprob_token_ids: list[int] | None = Field(
1042
+ default=None,
1043
+ description=(
1044
+ "Specific vocab token IDs to return logprobs for at each generated "
1045
+ "position, in addition to the sampled token. Requires "
1046
+ "`logprobs=True`."
1047
+ ),
1048
+ )
1049
max_tokens: int | None = None
1050
max_completion_tokens: int | None = None
1051
n: int | None = 1
@@ -1058,6 +1095,10 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
1095
"Batch chat completions do not support beam search. "
1096
"Please set `use_beam_search` to False."
1097
)
1098
+ if data.get("logprob_token_ids") and not data.get("logprobs"):
1099
+ raise ValueError(
1100
+ "when using `logprob_token_ids`, `logprobs` must be set to true."
1101
+ )
1102
response_format = data.get("response_format")
1103
rf_type = (
1104
response_format.get("type")
vllm/entrypoints/openai/chat_completion/serving.py
+14
-3
@@ -568,13 +568,16 @@ class OpenAIServingChat(GenerateBaseServing):
568
if finish_reason_sent[i]:
569
continue
570
571
- if request.logprobs and request.top_logprobs is not None:
571
+ if request.logprobs and (
572
+ request.top_logprobs is not None or request.logprob_token_ids
573
+ ):
574
assert output.logprobs is not None, "Did not output logprobs"
575
logprobs = self._create_chat_logprobs(
576
token_ids=output.token_ids,
577
top_logprobs=output.logprobs,
578
tokenizer=tokenizer,
579
num_output_top_logprobs=request.top_logprobs,
580
+ logprob_token_ids=request.logprob_token_ids,
581
return_as_token_id=request.return_tokens_as_token_ids,
582
)
583
else:
@@ -862,12 +865,15 @@ class OpenAIServingChat(GenerateBaseServing):
865
token_ids = output.token_ids
866
out_logprobs = output.logprobs
867
865
- if request.logprobs and request.top_logprobs is not None:
868
+ if request.logprobs and (
869
+ request.top_logprobs is not None or request.logprob_token_ids
870
+ ):
871
assert out_logprobs is not None, "Did not output logprobs"
872
logprobs = self._create_chat_logprobs(
873
token_ids=token_ids,
874
top_logprobs=out_logprobs,
875
num_output_top_logprobs=request.top_logprobs,
876
+ logprob_token_ids=request.logprob_token_ids,
877
tokenizer=tokenizer,
878
return_as_token_id=request.return_tokens_as_token_ids,
879
)
@@ -1101,6 +1107,7 @@ class OpenAIServingChat(GenerateBaseServing):
1107
top_logprobs: int | None,
1108
tokenizer: TokenizerLike | None,
1109
should_return_as_token_id: bool,
1110
+ return_all: bool = False,
1111
) -> list[ChatCompletionLogProb]:
1112
return [
1113
ChatCompletionLogProb(
@@ -1116,7 +1123,9 @@ class OpenAIServingChat(GenerateBaseServing):
1123
bytes=list(token.encode("utf-8", errors="replace")),
1124
)
1125
for i, p in enumerate(logprobs.items())
1119
- if (top_logprobs and i < top_logprobs or top_logprobs == -1)
1126
+ if return_all
1127
+ or top_logprobs == -1
1128
+ or (top_logprobs is not None and i < top_logprobs)
1129
]
1130
1131
def _create_chat_logprobs(
@@ -1125,6 +1134,7 @@ class OpenAIServingChat(GenerateBaseServing):
1134
top_logprobs: GenericSequence[dict[int, Logprob] | None],
1135
tokenizer: TokenizerLike | None,
1136
num_output_top_logprobs: int | None = None,
1137
+ logprob_token_ids: list[int] | None = None,
1138
return_as_token_id: bool | None = None,
1139
) -> ChatCompletionLogProbs:
1140
"""Create OpenAI-style logprobs."""
@@ -1177,6 +1187,7 @@ class OpenAIServingChat(GenerateBaseServing):
1187
num_output_top_logprobs,
1188
tokenizer,
1189
should_return_as_token_id,
1190
+ return_all=bool(logprob_token_ids),
1191
),
1192
)
1193
)
vllm/entrypoints/openai/completion/protocol.py
+38
-1
@@ -95,6 +95,19 @@ class CompletionRequest(OpenAIBaseModel):
95
)
96
allowed_token_ids: list[int] | None = None
97
prompt_logprobs: int | None = None
98
+ logprob_token_ids: list[int] | None = Field(
99
+ default=None,
100
+ description=(
101
+ "Specific vocab token IDs to return logprobs for at each generated "
102
+ "position, in addition to the sampled token. More efficient than "
103
+ "requesting the full vocab when only a small fixed label set is "
104
+ "needed (e.g. multilabel "
105
+ "scoring where each label corresponds to a known vocab id). When "
106
+ "set, this explicit token selection takes precedence over the "
107
+ "natural top-k selected by `logprobs`. Requires `logprobs` to be "
108
+ "set."
109
+ ),
110
+ )
111
bad_words: list[str] = Field(default_factory=list)
112
# --8<-- [end:completion-sampling-params]
113
@@ -368,11 +381,12 @@ class CompletionRequest(OpenAIBaseModel):
381
seed=self.seed,
382
stop=self.stop,
383
stop_token_ids=stop_token_ids,
371
- logprobs=self.logprobs,
384
+ logprobs=None if self.logprob_token_ids else self.logprobs,
385
ignore_eos=self.ignore_eos,
386
max_tokens=max_tokens if not echo_without_generation else 1,
387
min_tokens=self.min_tokens,
388
prompt_logprobs=prompt_logprobs,
389
+ logprob_token_ids=self.logprob_token_ids or None,
390
skip_special_tokens=self.skip_special_tokens,
391
spaces_between_special_tokens=self.spaces_between_special_tokens,
392
include_stop_str_in_output=self.include_stop_str_in_output,
@@ -459,6 +473,29 @@ class CompletionRequest(OpenAIBaseModel):
473
@model_validator(mode="before")
474
@classmethod
475
def check_logprobs(cls, data):
476
+ if data.get("logprob_token_ids") and data.get("use_beam_search"):
477
+ raise VLLMValidationError(
478
+ "`logprob_token_ids` is not supported with beam search.",
479
+ parameter="logprob_token_ids",
480
+ )
481
+
482
+ if (
483
+ data.get("logprob_token_ids")
484
+ and data.get("echo")
485
+ and data.get("max_tokens") == 0
486
+ ):
487
+ raise VLLMValidationError(
488
+ "`logprob_token_ids` is not supported when `echo=True` and "
489
+ "`max_tokens=0` because no output tokens are generated.",
490
+ parameter="logprob_token_ids",
491
+ )
492
+
493
+ if data.get("logprob_token_ids") and data.get("logprobs") is None:
494
+ raise VLLMValidationError(
495
+ "when using `logprob_token_ids`, `logprobs` must be set.",
496
+ parameter="logprob_token_ids",
497
+ )
498
+
499
if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
500
if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
501
raise VLLMValidationError(
vllm/entrypoints/openai/completion/serving.py
+4
-1
@@ -386,6 +386,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
386
top_logprobs=out_logprobs,
387
num_output_top_logprobs=request.logprobs,
388
tokenizer=tokenizer,
389
+ logprob_token_ids=request.logprob_token_ids,
390
initial_text_offset=previous_text_lens[i],
391
return_as_token_id=request.return_tokens_as_token_ids,
392
)
@@ -560,6 +561,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
561
top_logprobs=out_logprobs,
562
tokenizer=tokenizer,
563
num_output_top_logprobs=request.logprobs,
564
+ logprob_token_ids=request.logprob_token_ids,
565
return_as_token_id=request.return_tokens_as_token_ids,
566
)
567
else:
@@ -653,6 +655,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
655
top_logprobs: GenericSequence[dict[int, Logprob] | None],
656
num_output_top_logprobs: int,
657
tokenizer: TokenizerLike | None,
658
+ logprob_token_ids: list[int] | None = None,
659
initial_text_offset: int = 0,
660
return_as_token_id: bool | None = None,
661
) -> CompletionLogProbs:
@@ -717,7 +720,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
720
return_as_token_id=should_return_as_token_id,
721
): max(top_lp[1].logprob, -9999.0)
722
for i, top_lp in enumerate(step_top_logprobs.items())
720
- if num_output_top_logprobs >= i
723
+ if logprob_token_ids or num_output_top_logprobs >= i
724
}
725
)
726
vllm/sampling_params.py
+2
@@ -383,6 +383,7 @@ class SamplingParams(
383
extra_args: dict[str, Any] | None = None,
384
skip_clone: bool = False,
385
repetition_detection: RepetitionDetectionParams | None = None,
386
+ logprob_token_ids: list[int] | None = None,
387
) -> "SamplingParams":
388
if logit_bias is not None:
389
# Fast path uses a dict comprehension; on failure we iterate once
@@ -433,6 +434,7 @@ class SamplingParams(
434
min_tokens=min_tokens,
435
logprobs=logprobs,
436
prompt_logprobs=prompt_logprobs,
437
+ logprob_token_ids=logprob_token_ids,
438
detokenize=detokenize,
439
skip_special_tokens=skip_special_tokens,
440
spaces_between_special_tokens=spaces_between_special_tokens,