Skip to content

Commit

Permalink
[Bugfix] Fix vLLM UsageInfo and logprobs None AssertionError with emp…
Browse files Browse the repository at this point in the history
…ty token_ids (vllm-project#9034)

Co-authored-by: Nick Hill <[email protected]>
Signed-off-by: Amit Garg <[email protected]>
  • Loading branch information
2 people authored and garg-amit committed Oct 28, 2024
1 parent c9929b8 commit 42695fc
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 2 deletions.
126 changes: 126 additions & 0 deletions tests/entrypoints/openai/test_chunked_prompt.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions vllm/entrypoints/openai/serving_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ async def chat_completion_stream_generator(
logprobs = None

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]

# handle streaming deltas for tools with named tool_choice
Expand Down
7 changes: 5 additions & 2 deletions vllm/entrypoints/openai/serving_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,6 @@ async def completion_stream_generator(

for output in res.outputs:
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
if request.echo and request.max_tokens == 0:
Expand Down Expand Up @@ -307,6 +305,11 @@ async def completion_stream_generator(
delta_token_ids = output.token_ids
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:
assert out_logprobs is not None, (
"Did not output logprobs")
Expand Down
3 changes: 3 additions & 0 deletions vllm/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,9 @@ def get_output_token_ids_to_return(
# (which is what we have most of the time)
return self.data._cached_all_token_ids[-1]

if num_new_tokens == 0:
return []

return self.data._cached_all_token_ids[-num_new_tokens:]

def hash_of_block(self, logical_idx: int) -> int:
Expand Down

0 comments on commit 42695fc

Please sign in to comment.