mirror of
https://git.datalinker.icu/vllm-project/vllm.git
synced 2026-07-20 12:47:17 +08:00
[Bugfix] Fix vLLM UsageInfo and logprobs None AssertionError with empty token_ids (#9034)
Co-authored-by: Nick Hill <nickhill@us.ibm.com>
This commit is contained in:
parent
22f8a69549
commit
ba30942240
126
tests/entrypoints/openai/test_chunked_prompt.py
Normal file
126
tests/entrypoints/openai/test_chunked_prompt.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import openai # use the official client for correctness check
|
||||||
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
|
||||||
|
from ...utils import RemoteOpenAIServer
|
||||||
|
|
||||||
|
# any model with a chat template should work here
|
||||||
|
MODEL_NAME = "HuggingFaceH4/zephyr-7b-beta"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def server():
|
||||||
|
args = [
|
||||||
|
# use half precision for speed and memory savings in CI environment
|
||||||
|
"--dtype",
|
||||||
|
"bfloat16",
|
||||||
|
"--max-model-len",
|
||||||
|
"8192",
|
||||||
|
"--enforce-eager",
|
||||||
|
# lora config below
|
||||||
|
"--max-num-seqs",
|
||||||
|
"128",
|
||||||
|
"--enable-chunked-prefill",
|
||||||
|
"--max-num-batched-tokens",
|
||||||
|
"1000",
|
||||||
|
# large prompts create a lot of output
|
||||||
|
"--disable-log-requests",
|
||||||
|
]
|
||||||
|
|
||||||
|
with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
|
||||||
|
yield remote_server
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def client(server):
|
||||||
|
async with server.get_async_client() as async_client:
|
||||||
|
yield async_client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_completion_stream_options_and_logprobs_with_long_prompts(
|
||||||
|
client: openai.AsyncOpenAI):
|
||||||
|
# Test stream with long prompt
|
||||||
|
prompt = "What is the capital of France?" * 400
|
||||||
|
|
||||||
|
stream = await client.completions.create(
|
||||||
|
model=MODEL_NAME,
|
||||||
|
prompt=prompt,
|
||||||
|
max_tokens=5,
|
||||||
|
temperature=0.0,
|
||||||
|
stream=True,
|
||||||
|
stream_options={
|
||||||
|
"include_usage": True,
|
||||||
|
"continuous_usage_stats": True,
|
||||||
|
},
|
||||||
|
logprobs=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokens_received = 0
|
||||||
|
finished = False
|
||||||
|
async for chunk in stream:
|
||||||
|
assert chunk.usage.prompt_tokens >= 0
|
||||||
|
assert chunk.usage.completion_tokens >= 0
|
||||||
|
assert chunk.usage.total_tokens == (chunk.usage.prompt_tokens +
|
||||||
|
chunk.usage.completion_tokens)
|
||||||
|
if not finished:
|
||||||
|
tokens_received += 1
|
||||||
|
assert chunk.choices[0].text
|
||||||
|
|
||||||
|
if chunk.choices[0].finish_reason is not None:
|
||||||
|
finished = True
|
||||||
|
|
||||||
|
if finished:
|
||||||
|
assert chunk.usage.completion_tokens == tokens_received
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_chat_completion_stream_options_and_logprobs_with_long_prompts(
|
||||||
|
client: openai.AsyncOpenAI):
|
||||||
|
# Test stream with long prompt
|
||||||
|
messages = [{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a helpful assistant."
|
||||||
|
}, {
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is the capital of France?" * 400
|
||||||
|
}]
|
||||||
|
stream = await client.chat.completions.create(
|
||||||
|
model=MODEL_NAME,
|
||||||
|
messages=messages,
|
||||||
|
max_tokens=5,
|
||||||
|
temperature=0.0,
|
||||||
|
stream=True,
|
||||||
|
stream_options={
|
||||||
|
"include_usage": True,
|
||||||
|
"continuous_usage_stats": True,
|
||||||
|
},
|
||||||
|
logprobs=True,
|
||||||
|
top_logprobs=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
tokens_received = 0
|
||||||
|
empty_chunks_received = 0
|
||||||
|
finished = False
|
||||||
|
async for chunk in stream:
|
||||||
|
assert chunk.usage.prompt_tokens >= 0
|
||||||
|
assert chunk.usage.completion_tokens >= 0
|
||||||
|
assert chunk.usage.total_tokens == (chunk.usage.prompt_tokens +
|
||||||
|
chunk.usage.completion_tokens)
|
||||||
|
|
||||||
|
if not finished:
|
||||||
|
if chunk.choices[0].delta.content == "":
|
||||||
|
# when there is no tokens generated
|
||||||
|
assert chunk.usage.completion_tokens == 0
|
||||||
|
assert chunk.choices[0].logprobs is None
|
||||||
|
empty_chunks_received += 1
|
||||||
|
else:
|
||||||
|
tokens_received += 1
|
||||||
|
|
||||||
|
if chunk.choices[0].finish_reason is not None:
|
||||||
|
finished = True
|
||||||
|
|
||||||
|
if finished:
|
||||||
|
assert chunk.usage.completion_tokens == tokens_received
|
||||||
|
|
||||||
|
assert empty_chunks_received <= 1
|
||||||
@ -435,6 +435,12 @@ class OpenAIServingChat(OpenAIServing):
|
|||||||
logprobs = None
|
logprobs = None
|
||||||
|
|
||||||
delta_text = output.text
|
delta_text = output.text
|
||||||
|
|
||||||
|
if not delta_text and not output.token_ids and \
|
||||||
|
not previous_num_tokens[i]:
|
||||||
|
# Chunked prefill case, don't return empty chunks
|
||||||
|
continue
|
||||||
|
|
||||||
delta_message: Optional[DeltaMessage]
|
delta_message: Optional[DeltaMessage]
|
||||||
|
|
||||||
# handle streaming deltas for tools with named tool_choice
|
# handle streaming deltas for tools with named tool_choice
|
||||||
|
|||||||
@ -274,8 +274,6 @@ class OpenAIServingCompletion(OpenAIServing):
|
|||||||
|
|
||||||
for output in res.outputs:
|
for output in res.outputs:
|
||||||
i = output.index + prompt_idx * num_choices
|
i = output.index + prompt_idx * num_choices
|
||||||
# TODO(simon): optimize the performance by avoiding full
|
|
||||||
# text O(n^2) sending.
|
|
||||||
|
|
||||||
assert request.max_tokens is not None
|
assert request.max_tokens is not None
|
||||||
if request.echo and request.max_tokens == 0:
|
if request.echo and request.max_tokens == 0:
|
||||||
@ -307,6 +305,11 @@ class OpenAIServingCompletion(OpenAIServing):
|
|||||||
delta_token_ids = output.token_ids
|
delta_token_ids = output.token_ids
|
||||||
out_logprobs = output.logprobs
|
out_logprobs = output.logprobs
|
||||||
|
|
||||||
|
if not delta_text and not delta_token_ids \
|
||||||
|
and not previous_num_tokens[i]:
|
||||||
|
# Chunked prefill case, don't return empty chunks
|
||||||
|
continue
|
||||||
|
|
||||||
if request.logprobs is not None:
|
if request.logprobs is not None:
|
||||||
assert out_logprobs is not None, (
|
assert out_logprobs is not None, (
|
||||||
"Did not output logprobs")
|
"Did not output logprobs")
|
||||||
|
|||||||
@ -532,6 +532,9 @@ class Sequence:
|
|||||||
# (which is what we have most of the time)
|
# (which is what we have most of the time)
|
||||||
return self.data._cached_all_token_ids[-1]
|
return self.data._cached_all_token_ids[-1]
|
||||||
|
|
||||||
|
if num_new_tokens == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
return self.data._cached_all_token_ids[-num_new_tokens:]
|
return self.data._cached_all_token_ids[-num_new_tokens:]
|
||||||
|
|
||||||
def hash_of_block(self, logical_idx: int) -> int:
|
def hash_of_block(self, logical_idx: int) -> int:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user