Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 76 additions & 25 deletions .buildkite/test_areas/rust_frontend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ depends_on:
steps:
- label: ":nvidia: (H200 MIG 18GB) Rust Frontend OpenAI Coverage"
key: rust-frontend-openai-coverage
timeout_in_minutes: 30
timeout_in_minutes: 50
device: h200_18gb
working_dir: "/vllm-workspace/tests"
source_file_dependencies:
Expand All @@ -18,35 +18,53 @@ steps:
- tests/entrypoints/openai/chat_completion/test_chat_completion.py
- tests/entrypoints/openai/chat_completion/test_include_reasoning.py
- tests/entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py
- tests/entrypoints/openai/chat_completion/test_serving_chat.py
- tests/entrypoints/openai/chat_completion/test_chat_echo.py
- tests/entrypoints/openai/chat_completion/test_logprob_token_ids.py
- tests/entrypoints/openai/chat_completion/test_thinking_token_budget.py
- tests/entrypoints/openai/completion/test_completion.py
- tests/entrypoints/openai/completion/test_tensorizer_entrypoint.py
- tests/entrypoints/openai/test_chunked_prompt.py

# - tests/entrypoints/openai/completion/test_prompt_validation.py
- tests/entrypoints/launchers/test_shutdown.py
- tests/entrypoints/openai/test_return_token_ids.py
- tests/entrypoints/serve/instrumentator/test_uds.py
- tests/v1/sample/test_logprobs_e2e.py
- tests/v1/logits_processors/test_custom_online.py
- tests/v1/logits_processors/utils.py
commands:
- export VLLM_USE_RUST_FRONTEND=1
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -v -s benchmarks/test_serve_cli.py -k "not insecure and not (test_bench_serve and not test_bench_serve_chat)"
- pytest -v -s entrypoints/openai/chat_completion/test_chat_completion.py -k "not test_invalid_json_schema and not test_invalid_regex and not test_kv_transfer_prompt_token_ids_round_trip and not test_kv_transfer_prompt_token_ids_streaming"
- pytest -v -s benchmarks/test_serve_cli.py
# The Rust frontend re-renders messages instead of reusing kv_transfer_params["prompt_token_ids"].
- pytest -v -s entrypoints/openai/chat_completion/test_chat_completion.py -k "not test_kv_transfer_prompt_token_ids_round_trip and not test_kv_transfer_prompt_token_ids_streaming"
- pytest -v -s entrypoints/openai/chat_completion/test_include_reasoning.py
- pytest -v -s entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py -k "not multiple"
- pytest -v -s entrypoints/openai/chat_completion/test_serving_chat.py -k "test_chat_per_request_metrics_follow_server_flag or test_streaming_reasoning_usage_counts_across_deltas or test_completion_tokens_details"
- pytest -v -s entrypoints/openai/chat_completion/test_chat_echo.py
- pytest -v -s entrypoints/openai/chat_completion/test_logprob_token_ids.py
# The Qwen3.5-35B FP8 MTP case needs two GPUs.
- pytest -v -s entrypoints/openai/chat_completion/test_thinking_token_budget.py -k "not qwen35"
# The Rust frontend drops prompt_tokens_details when nothing was cached, rejects batched
# prompts, and supports only n=1 (so no parallel sampling or beam search).
- pytest -v -s entrypoints/openai/completion/test_completion.py -k "not test_single_completion and not test_prompt_logprobs_completion and not test_parallel and not test_batch_completions"
- pytest -v -s entrypoints/openai/completion/test_tensorizer_entrypoint.py --timeout=900 --timeout-method=thread
- pytest -v -s entrypoints/openai/test_chunked_prompt.py

# - pytest -v -s entrypoints/openai/completion/test_prompt_validation.py -k "not prompt_embeds"
- pytest -v -s entrypoints/launchers/test_shutdown.py -k "not engine_failure and not test_abort_timeout_exits_quickly"
# test_comparison streams differently: Rust emits a separate first (prompt_token_ids) chunk and
# finish chunk without logprobs, while the test reads `logprobs.tokens` on every chunk.
- pytest -v -s entrypoints/openai/test_return_token_ids.py -k "not test_comparison"
# test_shutdown_on_engine_failure launches the Python API server directly, not the Rust frontend.
- pytest -v -s entrypoints/launchers/test_shutdown.py -k "not engine_failure"
- pytest -v -s entrypoints/openai/test_return_token_ids.py
- pytest -v -s entrypoints/serve/instrumentator/test_uds.py
- pytest -v -s v1/sample/test_logprobs_e2e.py -k "test_prompt_logprobs_e2e_server"
# The Rust frontend cannot run the logits processor's validate_params, so an invalid argument
# reaches the engine instead of returning 400.
- pytest -v -s v1/logits_processors/test_custom_online.py -k "not test_invalid_custom_logitsproc_arg"
mirror:
amd:
label: ":amd: (MI355 DPX) Rust Frontend OpenAI Coverage"
dind: false
device: mi355_dpx
timeout_in_minutes: 35
timeout_in_minutes: 55
depends_on:
- image-build-amd
source_file_dependencies:
Expand All @@ -60,16 +78,23 @@ steps:
- tests/entrypoints/openai/chat_completion/test_chat_completion.py
- tests/entrypoints/openai/chat_completion/test_include_reasoning.py
- tests/entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py
- tests/entrypoints/openai/chat_completion/test_serving_chat.py
- tests/entrypoints/openai/chat_completion/test_chat_echo.py
- tests/entrypoints/openai/chat_completion/test_logprob_token_ids.py
- tests/entrypoints/openai/chat_completion/test_thinking_token_budget.py
- tests/entrypoints/openai/completion/test_completion.py
- tests/entrypoints/openai/completion/test_tensorizer_entrypoint.py
- tests/entrypoints/openai/test_chunked_prompt.py
- tests/entrypoints/launchers/test_shutdown.py
- tests/entrypoints/openai/test_return_token_ids.py
- tests/entrypoints/serve/instrumentator/test_uds.py
- tests/v1/sample/test_logprobs_e2e.py
- tests/v1/logits_processors/test_custom_online.py
- tests/v1/logits_processors/utils.py
- vllm/platforms/rocm.py

- label: ":nvidia: (H200 MIG 18GB) Rust Frontend Serve/Admin Coverage"
key: rust-frontend-serve-admin-coverage
timeout_in_minutes: 25
timeout_in_minutes: 45
device: h200_18gb
working_dir: "/vllm-workspace/tests"
source_file_dependencies:
Expand All @@ -83,23 +108,32 @@ steps:
- tests/entrypoints/serve/instrumentator/test_basic.py
- tests/entrypoints/serve/instrumentator/test_metrics.py
- tests/entrypoints/serve/tokenize/test_tokenization.py
- tests/entrypoints/serve/middleware/test_optional_middleware.py
- tests/entrypoints/launchers/api_server/test_multi_api_servers.py
- tests/entrypoints/rl/return_routed_experts/test_scale_out_api.py
commands:
- export VLLM_USE_RUST_FRONTEND=1
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- PYTHONPATH=/vllm-workspace pytest -v -s entrypoints/serve/dev/rpc/test_collective_rpc.py
# server_load can be flaky under the Rust frontend; keep it excluded for now.
- pytest -v -s entrypoints/serve/instrumentator/test_basic.py -k "not server_load"
# test_generate_logprobs expects Python-style top_logprobs truncation (dedup sampled + cap at max(k, 1)).
- pytest -v -s entrypoints/scale_out/token_in_token_out/test_serving_tokens.py -k "not stream and not lora and not test_generate_logprobs and not stop_string_workflow"
- pytest -v -s entrypoints/serve/instrumentator/test_metrics.py -k "text and not show and not run_batch and not test_metrics_counts and not test_metrics_exist"
# /tokenizer_info is not implemented in the Rust frontend (the CLI flag is accepted as a no-op).
- pytest -v -s entrypoints/serve/instrumentator/test_basic.py
# The Rust LoRA request still carries the removed tensorizer_config_dict field, so --lora-modules fails at startup.
- pytest -v -s entrypoints/scale_out/token_in_token_out/test_serving_tokens.py -k "not lora"
# run_batch drives the Python batch runner, not a server. test_metrics_exist needs
# vllm:admission_rejections (text) and image inputs (multimodal), which the Rust frontend lacks.
- pytest -v -s entrypoints/serve/instrumentator/test_metrics.py -k "not run_batch and not test_metrics_exist"
# /tokenizer_info returns 501 in the Rust frontend.
- pytest -v -s entrypoints/serve/tokenize/test_tokenization.py -k "not tokenizer_info"
# test_v2_endpoint_accepts_valid_api_token calls /v2/embed; the Rust frontend serves no pooling routes.
- pytest -v -s entrypoints/serve/middleware/test_optional_middleware.py -k "not test_v2_endpoint_accepts_valid_api_token"
# The Rust frontend ignores --api-server-count and runs one server, so this only checks the flag is accepted.
- pytest -v -s entrypoints/launchers/api_server/test_multi_api_servers.py
- pytest -v -s entrypoints/rl/return_routed_experts/test_scale_out_api.py
mirror:
amd:
label: ":amd: (MI355 DPX) Rust Frontend Serve/Admin Coverage"
dind: false
device: mi355_dpx
timeout_in_minutes: 30
timeout_in_minutes: 50
depends_on:
- image-build-amd
source_file_dependencies:
Expand All @@ -113,6 +147,9 @@ steps:
- tests/entrypoints/serve/instrumentator/test_basic.py
- tests/entrypoints/serve/instrumentator/test_metrics.py
- tests/entrypoints/serve/tokenize/test_tokenization.py
- tests/entrypoints/serve/middleware/test_optional_middleware.py
- tests/entrypoints/launchers/api_server/test_multi_api_servers.py
- tests/entrypoints/rl/return_routed_experts/test_scale_out_api.py
- vllm/platforms/rocm.py

- label: ":nvidia: (H200 MIG 18GB) Rust Frontend Core Correctness"
Expand Down Expand Up @@ -147,24 +184,33 @@ steps:
- label: ":nvidia: (H200 MIG 35GB) Rust Frontend Tool Use"
key: rust-frontend-tool-use
device: h200_35gb
timeout_in_minutes: 25
timeout_in_minutes: 35
working_dir: "/vllm-workspace/tests"
source_file_dependencies:
- rust/
- vllm/entrypoints/openai/
- vllm/tool_parsers/
- tests/utils.py
- tests/tool_use/
- tests/entrypoints/openai/chat_completion/test_completion_with_function_calling.py
- tests/entrypoints/tool_parsers/test_granite4_tool_parser.py
- tests/entrypoints/tool_parsers/test_hermes_tool_parser.py
commands:
- export VLLM_USE_RUST_FRONTEND=1
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -v -s tool_use --ignore=tool_use/mistral --models llama3.2 -k "not test_response_format_with_tool_choice_required and not test_parallel_tool_calls_false and not test_tool_call_and_choice"
# The Rust frontend has no `mistral` tokenizer mode or renderer.
- pytest -v -s tool_use --ignore=tool_use/mistral --models llama3.2
# The Rust renderer rejects empty tool-call arguments in the history (Python treats them as {}).
- pytest -v -s entrypoints/openai/chat_completion/test_completion_with_function_calling.py -k "not test_no_args_tool_call"
- pytest -v -s entrypoints/tool_parsers/test_granite4_tool_parser.py
# The llama cases use --lora-modules, which fails at startup until the Rust LoRA request drops tensorizer_config_dict.
- pytest -v -s entrypoints/tool_parsers/test_hermes_tool_parser.py -k granite4
mirror:
amd:
label: ":amd: (MI355 DPX) Rust Frontend Tool Use"
dind: false
device: mi355_dpx
timeout_in_minutes: 30
timeout_in_minutes: 40
depends_on:
- image-build-amd
source_file_dependencies:
Expand All @@ -173,6 +219,9 @@ steps:
- vllm/tool_parsers/
- tests/utils.py
- tests/tool_use/
- tests/entrypoints/openai/chat_completion/test_completion_with_function_calling.py
- tests/entrypoints/tool_parsers/test_granite4_tool_parser.py
- tests/entrypoints/tool_parsers/test_hermes_tool_parser.py
- vllm/platforms/rocm.py

- label: ":nvidia: (L4) [Nightly] Rust Frontend Distributed"
Expand Down Expand Up @@ -201,9 +250,11 @@ steps:
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- export NCCL_CUMEM_HOST_ENABLE=0
- TP_SIZE=1 DP_SIZE=4 pytest -v -s v1/distributed/test_dense_dp_world_size.py
- TP_SIZE=1 DP_SIZE=4 pytest -v -s v1/distributed/test_internal_lb_dp.py -k "not 4 and not server_info"
- TP_SIZE=1 DP_SIZE=2 pytest -v -s v1/distributed/test_external_lb_dp.py -k "not 4 and not server_info"
- TP_SIZE=1 DP_SIZE=4 pytest -v -s v1/distributed/test_hybrid_lb_dp.py -k "not 4 and not server_info"
# /server_info reports the Rust frontend's own config, which has no parallel_config. The Rust frontend
# ignores --api-server-count, so the api-server-count=4 cases run against one multithreaded server.
- TP_SIZE=1 DP_SIZE=4 pytest -v -s v1/distributed/test_internal_lb_dp.py -k "not server_info"
- TP_SIZE=1 DP_SIZE=2 pytest -v -s v1/distributed/test_external_lb_dp.py -k "not server_info"
- TP_SIZE=1 DP_SIZE=4 pytest -v -s v1/distributed/test_hybrid_lb_dp.py -k "not server_info"
mirror:
amd:
# Run on PRs even when the L4 step is optional (nightly only).
Expand Down
2 changes: 1 addition & 1 deletion tests/entrypoints/openai/chat_completion/test_chat_echo.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ async def test_top_logprobs(client: openai.AsyncOpenAI):
max_tokens=1,
extra_body={
"top_logprobs": -1,
"logprobs": "true",
"logprobs": True,
},
)
assert completion.choices[0].logprobs is not None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ async def test_named_tool_use(
tool_choice=tool_choice,
)
message = chat_completion.choices[0].message
assert len(message.content) == 0
assert not message.content
json_string = message.tool_calls[0].function.arguments
json1 = json.loads(json_string)
jsonschema.validate(instance=json1, schema=sample_json_schema)
Expand Down
16 changes: 10 additions & 6 deletions tests/entrypoints/openai/completion/test_completion.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

from contextlib import suppress

import openai # use the official client for correctness check
import pytest
Expand All @@ -14,7 +15,7 @@
from vllm.tokenizers import get_tokenizer

# any model with a chat template should work here
MODEL_NAME = "facebook/opt-125m"
MODEL_NAME = "Qwen/Qwen3-0.6B"


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -62,8 +63,8 @@ async def test_single_completion(client: openai.AsyncOpenAI, model_name: str) ->
assert choice.finish_reason == "length"
assert completion.usage is not None
assert completion.usage.completion_tokens == 5
assert completion.usage.prompt_tokens == 6
assert completion.usage.total_tokens == 11
assert completion.usage.prompt_tokens == 5
assert completion.usage.total_tokens == 10
assert completion.usage.prompt_tokens_details is not None
assert completion.usage.prompt_tokens_details.cached_tokens == 0

Expand Down Expand Up @@ -337,7 +338,8 @@ async def test_completion_streaming(
# finish reason should only return in last block
assert finish_reason_count == 1
assert chunk.choices[0].finish_reason == "length"
assert chunk.choices[0].text
# The last chunk may carry only the finish_reason (Rust); the joined text is
# checked against the non-streaming output below.
assert "".join(chunks) == single_output


Expand Down Expand Up @@ -551,7 +553,8 @@ async def test_completion_stream_options(client: openai.AsyncOpenAI, model_name:

# Test stream=False, stream_options=
# {"include_usage": None}
with pytest.raises(BadRequestError):
# A null option sets nothing: Python rejects it, Rust accepts it as unset.
with suppress(BadRequestError):
await client.completions.create(
model=model_name,
prompt=prompt,
Expand All @@ -575,7 +578,8 @@ async def test_completion_stream_options(client: openai.AsyncOpenAI, model_name:

# Test stream=False, stream_options=
# {"continuous_usage_stats": None}
with pytest.raises(BadRequestError):
# A null option sets nothing: Python rejects it, Rust accepts it as unset.
with suppress(BadRequestError):
await client.completions.create(
model=model_name,
prompt=prompt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ async def test_single_completion(client: openai.AsyncOpenAI, model_name: str):
assert len(completion.choices) == 1
assert len(completion.choices[0].text) >= 5
assert completion.choices[0].finish_reason == "length"
assert completion.usage == openai.types.CompletionUsage(
completion_tokens=5, prompt_tokens=6, total_tokens=11
)
# Compare the counts only: the Rust frontend also reports
# completion_tokens_details (reasoning_tokens=0), which Python omits.
assert completion.usage is not None
assert completion.usage.completion_tokens == 5
assert completion.usage.prompt_tokens == 6
assert completion.usage.total_tokens == 11
24 changes: 17 additions & 7 deletions tests/entrypoints/openai/test_chunked_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ async def test_completion_stream_options_and_logprobs_with_long_prompts(
chunk.usage.prompt_tokens + chunk.usage.completion_tokens
)
if not finished:
assert chunk.choices[0].text
# Count actual tokens from logprobs since multiple tokens
# can be batched into a single chunk
assert chunk.choices[0].logprobs and chunk.choices[0].logprobs.tokens
tokens_received += len(chunk.choices[0].logprobs.tokens)
# A final chunk may carry only the finish_reason with no text and
# no logprobs (Rust); every chunk that carries tokens is checked.
if chunk.choices[0].finish_reason is None or chunk.choices[0].text:
assert chunk.choices[0].text
# Count actual tokens from logprobs since multiple tokens
# can be batched into a single chunk
assert chunk.choices[0].logprobs and chunk.choices[0].logprobs.tokens
tokens_received += len(chunk.choices[0].logprobs.tokens)

if chunk.choices[0].finish_reason is not None:
finished = True
Expand Down Expand Up @@ -113,11 +116,18 @@ async def test_chat_completion_stream_options_and_logprobs_with_long_prompts(
)

if not finished:
if chunk.choices[0].delta.content == "":
# when there is no tokens generated
if chunk.choices[0].delta.content == "" or (
chunk.choices[0].delta.content is None
and chunk.choices[0].finish_reason is None
):
# when there is no tokens generated (the role-only first chunk;
# Python sends content="", Rust omits content)
assert chunk.usage.completion_tokens == 0
assert chunk.choices[0].logprobs is None
empty_chunks_received += 1
elif chunk.choices[0].delta.content is None:
# final chunk with only the finish_reason (Rust)
assert chunk.choices[0].logprobs is None
else:
# Count actual tokens from logprobs since multiple tokens
# can be batched into a single chunk
Expand Down
5 changes: 4 additions & 1 deletion tests/entrypoints/openai/test_return_token_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,10 @@ async def test_comparison_with_prompt_logprobs_and_logprobs(server):
streamed_logprob_token_ids = []
first_chunk = True
async for chunk in stream:
for token_str in chunk.choices[0].logprobs.tokens:
# A chunk may carry no logprobs, e.g. a leading chunk with only
# prompt_token_ids or a trailing chunk with only finish_reason.
logprobs = chunk.choices[0].logprobs
for token_str in logprobs.tokens if logprobs else []:
# Token format is "token_id:12345" when
# return_tokens_as_token_ids is True
if token_str.startswith("token_id:"):
Expand Down
14 changes: 5 additions & 9 deletions tests/entrypoints/serve/lora/test_lora_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,7 @@ async def load_and_run_adapter(adapter_name: str):
for i in range(10):
lora_tasks.append(asyncio.create_task(load_and_run_adapter(f"adapter_{i}")))

results, _ = await asyncio.wait(lora_tasks)

for r in results:
assert not isinstance(r, Exception), f"Got exception {r}"
await asyncio.gather(*lora_tasks)


@pytest.mark.asyncio
Expand Down Expand Up @@ -363,9 +360,8 @@ async def load_and_run_adapter(adapter_name: str):

lora_tasks = []
for i in range(3):
lora_tasks.append(asyncio.create_task(load_and_run_adapter(f"adapter_{i}")))

results, _ = await asyncio.wait(lora_tasks)
lora_tasks.append(
asyncio.create_task(load_and_run_adapter(f"beam_adapter_{i}"))
)

for r in results:
assert not isinstance(r, Exception), f"Got exception {r}"
await asyncio.gather(*lora_tasks)
Loading