2025-12-24 21:39:01 +09:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
"""
|
|
|
|
|
Unit tests for OmniOpenAIServingChat sampling params handling.
|
|
|
|
|
|
|
|
|
|
Tests that standard OpenAI API parameters (max_tokens, temperature, etc.)
|
|
|
|
|
are correctly applied to the comprehension stage while preserving YAML defaults.
|
|
|
|
|
"""
|
|
|
|
|
|
2026-07-30 15:08:12 +08:00
|
|
|
import asyncio
|
2026-06-21 22:08:26 +08:00
|
|
|
from types import SimpleNamespace
|
|
|
|
|
|
2025-12-24 21:39:01 +09:00
|
|
|
import pytest
|
2026-02-26 17:08:39 +08:00
|
|
|
from pytest_mock import MockerFixture
|
2026-07-30 15:08:12 +08:00
|
|
|
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
|
2025-12-24 21:39:01 +09:00
|
|
|
from vllm.sampling_params import SamplingParams
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
from tests.helpers.serving_chat import build_serving_chat
|
|
|
|
|
|
2026-02-09 16:49:45 +08:00
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
|
|
|
|
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
@pytest.fixture
|
2026-02-26 17:08:39 +08:00
|
|
|
def mock_comprehension_stage(mocker: MockerFixture):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Create a mock comprehension stage with is_comprehension=True."""
|
2026-02-26 17:08:39 +08:00
|
|
|
stage = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
stage.is_comprehension = True
|
|
|
|
|
stage.model_stage = "comprehension"
|
|
|
|
|
return stage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2026-02-26 17:08:39 +08:00
|
|
|
def mock_other_stage(mocker: MockerFixture):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Create a mock non-comprehension stage."""
|
2026-02-26 17:08:39 +08:00
|
|
|
stage = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
stage.is_comprehension = False
|
|
|
|
|
stage.model_stage = "other"
|
|
|
|
|
return stage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def default_comprehension_params():
|
|
|
|
|
"""Default sampling params for comprehension stage (from YAML)."""
|
|
|
|
|
return SamplingParams(
|
|
|
|
|
temperature=0.4,
|
|
|
|
|
top_p=0.9,
|
|
|
|
|
top_k=1,
|
2026-04-25 20:49:19 +08:00
|
|
|
max_tokens=4353,
|
2025-12-24 21:39:01 +09:00
|
|
|
seed=42,
|
|
|
|
|
repetition_penalty=1.05,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def default_other_params():
|
|
|
|
|
"""Default sampling params for non-comprehension stage (from YAML)."""
|
|
|
|
|
return SamplingParams(
|
|
|
|
|
temperature=0.9,
|
|
|
|
|
top_k=50,
|
|
|
|
|
max_tokens=4096,
|
|
|
|
|
seed=42,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2026-02-26 17:08:39 +08:00
|
|
|
def mock_engine_client(
|
|
|
|
|
mock_comprehension_stage,
|
|
|
|
|
mock_other_stage,
|
|
|
|
|
default_comprehension_params,
|
|
|
|
|
default_other_params,
|
|
|
|
|
mocker: MockerFixture,
|
|
|
|
|
):
|
2026-03-18 17:33:53 +08:00
|
|
|
"""Create mock engine client with stage_configs and default_sampling_params_list."""
|
2026-02-26 17:08:39 +08:00
|
|
|
engine_client = mocker.MagicMock()
|
2026-03-18 17:33:53 +08:00
|
|
|
engine_client.stage_configs = [mock_comprehension_stage, mock_other_stage]
|
2025-12-24 21:39:01 +09:00
|
|
|
engine_client.default_sampling_params_list = [
|
|
|
|
|
default_comprehension_params,
|
|
|
|
|
default_other_params,
|
|
|
|
|
]
|
|
|
|
|
return engine_client
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_serving_boundary_normalizes_declared_root_and_nested_extras(mock_engine_client):
|
|
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
serving_chat._diffusion_extra_body_params = frozenset({"cfg_text_scale", "negative_prompt"})
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
modalities=["image"],
|
|
|
|
|
num_inference_steps=7,
|
2026-08-07 17:07:23 +08:00
|
|
|
quality="high",
|
2026-07-30 15:08:12 +08:00
|
|
|
size="768x512",
|
|
|
|
|
negative_prompt="avoid blur",
|
|
|
|
|
lora={"name": "adapter"},
|
|
|
|
|
cfg_text_scale=7.0,
|
|
|
|
|
extra_body={"extra_args": {"sample_solver": "euler"}},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
normalized_extra_args, diffusion_request_args = serving_chat._normalize_diffusion_request_args(request)
|
|
|
|
|
|
|
|
|
|
assert normalized_extra_args == {
|
|
|
|
|
"cfg_text_scale": 7.0,
|
|
|
|
|
"sample_solver": "euler",
|
|
|
|
|
}
|
|
|
|
|
assert diffusion_request_args["cfg_text_scale"] == 7.0
|
|
|
|
|
assert diffusion_request_args["num_inference_steps"] == 7
|
2026-08-07 17:07:23 +08:00
|
|
|
assert diffusion_request_args["quality"] == "high"
|
2026-07-30 15:08:12 +08:00
|
|
|
assert diffusion_request_args["size"] == "768x512"
|
|
|
|
|
assert diffusion_request_args["negative_prompt"] == "avoid blur"
|
|
|
|
|
assert diffusion_request_args["lora"] == {"name": "adapter"}
|
|
|
|
|
assert diffusion_request_args["modalities"] == ["image"]
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_unknown_root_extra_does_not_claim_canonical_extra(mock_engine_client):
|
|
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
serving_chat._diffusion_extra_body_params = frozenset()
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
pipeline_option="ignored-root-value",
|
|
|
|
|
extra_args={"pipeline_option": "canonical-value"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
normalized_extra_args, diffusion_request_args = serving_chat._normalize_diffusion_request_args(request)
|
|
|
|
|
|
|
|
|
|
assert normalized_extra_args == {
|
|
|
|
|
"pipeline_option": "canonical-value",
|
|
|
|
|
}
|
|
|
|
|
assert "pipeline_option" not in diffusion_request_args
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_serving_boundary_rejects_invalid_quality(mock_engine_client):
|
|
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-08-07 17:07:23 +08:00
|
|
|
serving_chat._diffusion_extra_body_params = frozenset()
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
modalities=["image"],
|
|
|
|
|
quality="medium",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="quality must be one of"):
|
|
|
|
|
serving_chat._normalize_diffusion_request_args(request)
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_unregistered_cfg_scale_aliases_common_true_cfg_scale(mock_engine_client):
|
|
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
serving_chat._diffusion_extra_body_params = frozenset()
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
cfg_scale=7.0,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
normalized_extra_args, diffusion_request_args = serving_chat._normalize_diffusion_request_args(request)
|
|
|
|
|
|
|
|
|
|
assert normalized_extra_args == {}
|
|
|
|
|
assert diffusion_request_args == {"true_cfg_scale": 7.0}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
("registered", "request_kwargs"),
|
|
|
|
|
[
|
|
|
|
|
({"cfg_scale"}, {"cfg_scale": 7.0, "extra_args": {"cfg_scale": 8.0}}),
|
|
|
|
|
(set(), {"cfg_scale": 7.0, "true_cfg_scale": 2.0}),
|
|
|
|
|
],
|
|
|
|
|
ids=["registered-model-extra", "unregistered-common-alias"],
|
|
|
|
|
)
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_cfg_scale_owner_conflicts_are_rejected(mock_engine_client, registered, request_kwargs):
|
|
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
serving_chat._diffusion_extra_body_params = frozenset(registered)
|
|
|
|
|
request = ChatCompletionRequest(model="test", messages=[], **request_kwargs)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="provided more than once"):
|
|
|
|
|
serving_chat._normalize_diffusion_request_args(request)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("diffusion_mode", [True, False], ids=["pure", "mixed"])
|
|
|
|
|
def test_duplicate_extras_return_the_same_bad_request_before_dispatch(
|
2026-08-20 15:05:05 -05:00
|
|
|
mock_engine_client,
|
2026-07-30 15:08:12 +08:00
|
|
|
mocker: MockerFixture,
|
|
|
|
|
diffusion_mode: bool,
|
|
|
|
|
):
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
serving_chat._diffusion_mode = diffusion_mode
|
|
|
|
|
serving_chat._diffusion_extra_body_params = frozenset({"sample_solver"})
|
|
|
|
|
serving_chat.engine_client.stage_configs = [SimpleNamespace(stage_type="diffusion")]
|
|
|
|
|
check_model = mocker.patch.object(serving_chat, "_check_model", new=mocker.AsyncMock())
|
|
|
|
|
pure_dispatch = mocker.patch.object(
|
|
|
|
|
serving_chat,
|
|
|
|
|
"_create_diffusion_chat_completion",
|
|
|
|
|
new=mocker.AsyncMock(),
|
|
|
|
|
)
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
sample_solver="euler",
|
|
|
|
|
extra_args={"sample_solver": "ddim"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(serving_chat._create_chat_completion(request))
|
|
|
|
|
|
|
|
|
|
assert response.error.code == 400
|
|
|
|
|
assert response.error.message == (
|
|
|
|
|
'Diffusion request parameters were provided more than once: "sample_solver": '
|
|
|
|
|
"request.sample_solver, request.extra_args.sample_solver."
|
|
|
|
|
)
|
|
|
|
|
check_model.assert_not_awaited()
|
|
|
|
|
pure_dispatch.assert_not_awaited()
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_pure_consumer_preserves_defaults_and_separate_cfg_owners(mock_engine_client, mocker: MockerFixture):
|
2026-07-30 15:08:12 +08:00
|
|
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-07-30 15:08:12 +08:00
|
|
|
captured: dict[str, object] = {}
|
|
|
|
|
|
|
|
|
|
async def generate(**kwargs):
|
|
|
|
|
captured.update(kwargs)
|
|
|
|
|
if False:
|
|
|
|
|
yield None
|
|
|
|
|
|
|
|
|
|
serving_chat._diffusion_model_name = "test"
|
|
|
|
|
serving_chat._diffusion_engine = SimpleNamespace(
|
|
|
|
|
stage_configs=[SimpleNamespace(stage_type="diffusion")],
|
|
|
|
|
default_sampling_params_list=[
|
|
|
|
|
OmniDiffusionSamplingParams(extra_args={"solver": "euler", "stage_default": True}),
|
|
|
|
|
],
|
|
|
|
|
generate=generate,
|
|
|
|
|
)
|
|
|
|
|
serving_chat._diffusion_mode = True
|
|
|
|
|
serving_chat._diffusion_extra_body_params = frozenset({"cfg_scale"})
|
|
|
|
|
serving_chat._extract_diffusion_prompt_and_media = mocker.Mock(return_value=("prompt", [], [], []))
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[],
|
|
|
|
|
modalities=["image"],
|
|
|
|
|
num_inference_steps=7,
|
|
|
|
|
size="768x512",
|
|
|
|
|
negative_prompt="avoid blur",
|
|
|
|
|
cfg_scale=7.0,
|
|
|
|
|
true_cfg_scale=2.0,
|
2026-08-07 17:07:23 +08:00
|
|
|
quality="high",
|
2026-07-30 15:08:12 +08:00
|
|
|
extra_body={"extra_args": {"solver": "ddim"}},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = asyncio.run(serving_chat._create_chat_completion(request))
|
|
|
|
|
|
|
|
|
|
(sampling_params,) = captured["sampling_params_list"]
|
|
|
|
|
assert sampling_params.extra_args == {
|
|
|
|
|
"cfg_scale": 7.0,
|
|
|
|
|
"solver": "ddim",
|
|
|
|
|
"stage_default": True,
|
|
|
|
|
}
|
|
|
|
|
assert sampling_params.true_cfg_scale == 2.0
|
|
|
|
|
assert sampling_params.num_inference_steps == 7
|
2026-08-07 17:07:23 +08:00
|
|
|
assert sampling_params.quality == "high"
|
2026-07-30 15:08:12 +08:00
|
|
|
assert (sampling_params.height, sampling_params.width) == (512, 768)
|
|
|
|
|
assert captured["prompt"]["negative_prompt"] == "avoid blur"
|
|
|
|
|
assert response.error.message == "No output generated from AsyncOmni"
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_mixed_consumer_keeps_root_common_args_with_nested_extras(mock_engine_client, mocker: MockerFixture):
|
2026-07-30 15:08:12 +08:00
|
|
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
mock_engine_client.stage_configs = [
|
2026-07-30 15:08:12 +08:00
|
|
|
SimpleNamespace(stage_type="llm", is_comprehension=True),
|
|
|
|
|
SimpleNamespace(stage_type="diffusion", is_comprehension=False),
|
|
|
|
|
]
|
2026-08-20 15:05:05 -05:00
|
|
|
mock_engine_client.default_sampling_params_list = [
|
2026-07-30 15:08:12 +08:00
|
|
|
SamplingParams(),
|
|
|
|
|
OmniDiffusionSamplingParams(),
|
|
|
|
|
]
|
2026-08-20 15:05:05 -05:00
|
|
|
mock_engine_client.output_modalities = ["image"]
|
|
|
|
|
mock_engine_client.errored = False
|
|
|
|
|
mock_engine_client.renderer = SimpleNamespace(get_tokenizer=lambda: object())
|
2026-07-30 15:08:12 +08:00
|
|
|
|
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
|
|
|
|
|
|
async def results():
|
|
|
|
|
if False:
|
|
|
|
|
yield None
|
|
|
|
|
|
|
|
|
|
def generate(**kwargs):
|
|
|
|
|
captured.update(kwargs)
|
|
|
|
|
return results()
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
mock_engine_client.generate = generate
|
|
|
|
|
serving_chat = build_serving_chat(
|
|
|
|
|
engine_client=mock_engine_client,
|
2026-07-30 15:08:12 +08:00
|
|
|
models=SimpleNamespace(model_name=lambda _: "test"),
|
|
|
|
|
online_renderer=SimpleNamespace(validate_chat_template=lambda **_: None),
|
|
|
|
|
trust_request_chat_template=True,
|
|
|
|
|
)
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat._diffusion_mode = False
|
|
|
|
|
serving_chat._diffusion_extra_body_params = frozenset()
|
2026-07-30 15:08:12 +08:00
|
|
|
mocker.patch.multiple(
|
|
|
|
|
serving_chat,
|
|
|
|
|
_check_model=mocker.AsyncMock(return_value=None),
|
|
|
|
|
_maybe_get_adapters=mocker.Mock(return_value=None),
|
|
|
|
|
_effective_chat_template_kwargs=mocker.Mock(return_value={}),
|
|
|
|
|
_preprocess_chat=mocker.AsyncMock(return_value=([], [{"prompt": "raw"}])),
|
|
|
|
|
_base_request_id=mocker.Mock(return_value="test"),
|
|
|
|
|
_extract_diffusion_prompt_and_images_from_messages=mocker.Mock(return_value=("prompt", [])),
|
|
|
|
|
_log_inputs=mocker.Mock(),
|
|
|
|
|
chat_completion_full_generator=mocker.AsyncMock(return_value="done"),
|
|
|
|
|
)
|
|
|
|
|
request = ChatCompletionRequest(
|
|
|
|
|
model="test",
|
|
|
|
|
messages=[{"role": "user", "content": "prompt"}],
|
|
|
|
|
modalities=["image"],
|
|
|
|
|
num_inference_steps=7,
|
|
|
|
|
size="768x512",
|
|
|
|
|
negative_prompt="avoid blur",
|
|
|
|
|
extra_body={"extra_args": {"solver": "euler"}},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert asyncio.run(serving_chat._create_chat_completion(request)) == "done"
|
|
|
|
|
|
|
|
|
|
sampling_params_list = captured["sampling_params_list"]
|
|
|
|
|
assert sampling_params_list[0].extra_args == {"target_h": 512, "target_w": 768}
|
|
|
|
|
diffusion_params = sampling_params_list[1]
|
|
|
|
|
assert diffusion_params.num_inference_steps == 7
|
2026-08-07 17:07:23 +08:00
|
|
|
assert diffusion_params.quality is None
|
2026-07-30 15:08:12 +08:00
|
|
|
assert (diffusion_params.height, diffusion_params.width) == (512, 768)
|
|
|
|
|
assert diffusion_params.extra_args == {"solver": "euler"}
|
|
|
|
|
assert captured["prompt"]["negative_prompt"] == "avoid blur"
|
|
|
|
|
|
|
|
|
|
|
2025-12-24 21:39:01 +09:00
|
|
|
@pytest.fixture
|
2026-02-26 17:08:39 +08:00
|
|
|
def mock_request(mocker: MockerFixture):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Create a mock request with all OpenAI sampling params set to None."""
|
2026-02-26 17:08:39 +08:00
|
|
|
request = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
# OpenAI standard sampling fields
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
2026-02-10 16:08:20 +08:00
|
|
|
request.top_k = None
|
2025-12-24 21:39:01 +09:00
|
|
|
request.max_tokens = None
|
2026-02-10 16:08:20 +08:00
|
|
|
request.min_tokens = None
|
2025-12-24 21:39:01 +09:00
|
|
|
request.seed = None
|
2026-02-10 16:08:20 +08:00
|
|
|
request.ignore_eos = None
|
2025-12-24 21:39:01 +09:00
|
|
|
request.stop = None
|
2026-02-10 16:08:20 +08:00
|
|
|
request.stop_token_ids = None
|
2025-12-24 21:39:01 +09:00
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
# Must be real Python objects (not MagicMock) so the code's explicit-field
|
|
|
|
|
# and extra_body checks work correctly.
|
|
|
|
|
request.model_fields_set = set()
|
|
|
|
|
request.extra_body = {}
|
2025-12-24 21:39:01 +09:00
|
|
|
return request
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for _OPENAI_SAMPLING_FIELDS constant
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openai_sampling_fields_contains_expected_fields():
|
|
|
|
|
"""Test that _OPENAI_SAMPLING_FIELDS contains all expected OpenAI params."""
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
expected_fields = {
|
|
|
|
|
"temperature",
|
|
|
|
|
"top_p",
|
2026-02-10 16:08:20 +08:00
|
|
|
"top_k",
|
2025-12-24 21:39:01 +09:00
|
|
|
"max_tokens",
|
2026-02-10 16:08:20 +08:00
|
|
|
"min_tokens",
|
2025-12-24 21:39:01 +09:00
|
|
|
"seed",
|
2026-02-10 16:08:20 +08:00
|
|
|
"ignore_eos",
|
2025-12-24 21:39:01 +09:00
|
|
|
"stop",
|
2026-02-10 16:08:20 +08:00
|
|
|
"stop_token_ids",
|
2025-12-24 21:39:01 +09:00
|
|
|
"frequency_penalty",
|
|
|
|
|
"presence_penalty",
|
|
|
|
|
}
|
|
|
|
|
assert OmniOpenAIServingChat._OPENAI_SAMPLING_FIELDS == expected_fields
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for _build_sampling_params_list_from_request
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_preserves_yaml_defaults_when_no_request_params(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that YAML defaults are preserved when request has no params."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
assert len(result) == 2
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
assert comprehension_params.temperature == 0.4
|
|
|
|
|
assert comprehension_params.top_p == 0.9
|
|
|
|
|
assert comprehension_params.top_k == 1 # YAML custom param preserved
|
2026-04-25 20:49:19 +08:00
|
|
|
assert comprehension_params.max_tokens == 4353
|
2025-12-24 21:39:01 +09:00
|
|
|
assert comprehension_params.seed == 42
|
|
|
|
|
assert comprehension_params.repetition_penalty == 1.05 # YAML custom param preserved
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_temperature_overrides_yaml_default(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request temperature overrides YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.temperature = 0.8
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"temperature"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
assert comprehension_params.temperature == 0.8 # Overridden
|
|
|
|
|
assert comprehension_params.seed == 42 # Preserved from YAML
|
|
|
|
|
assert comprehension_params.top_k == 1 # YAML custom param preserved
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_top_p_overrides_yaml_default(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request top_p overrides YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.top_p = 0.95
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"top_p"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
assert comprehension_params.top_p == 0.95 # Overridden
|
|
|
|
|
assert comprehension_params.temperature == 0.4 # Preserved from YAML
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_max_tokens_overrides_yaml_default(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request max_tokens overrides YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.max_tokens = 100
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"max_tokens"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
assert result[0].max_tokens == 100
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_max_tokens_uses_yaml_default_when_not_specified(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that max_tokens falls back to YAML default when not in request."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
2026-04-25 20:49:19 +08:00
|
|
|
assert result[0].max_tokens == 4353
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_seed_overrides_yaml_default(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request seed overrides YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.seed = 123
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"seed"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
assert comprehension_params.seed == 123 # Overridden
|
|
|
|
|
assert comprehension_params.temperature == 0.4 # Preserved from YAML
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_frequency_penalty_overrides(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request frequency_penalty is applied."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.frequency_penalty = 0.5
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"frequency_penalty"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
assert result[0].frequency_penalty == 0.5
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_request_presence_penalty_overrides(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that request presence_penalty is applied."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.presence_penalty = 0.3
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"presence_penalty"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
assert result[0].presence_penalty == 0.3
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_non_comprehension_stages_use_cloned_defaults(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that non-comprehension stages always use cloned YAML defaults."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.max_tokens = 50
|
|
|
|
|
mock_request.temperature = 0.1
|
|
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
other_params = result[1]
|
|
|
|
|
assert other_params.temperature == 0.9 # YAML default (not affected by request)
|
|
|
|
|
assert other_params.max_tokens == 4096 # YAML default (not affected by request)
|
|
|
|
|
assert other_params.top_k == 50 # YAML default
|
|
|
|
|
assert other_params.seed == 42 # YAML default
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_multiple_params_override_together(mock_engine_client, mock_request):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that multiple request params can override together."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.max_tokens = 200
|
|
|
|
|
mock_request.temperature = 0.7
|
|
|
|
|
mock_request.top_p = 0.85
|
|
|
|
|
mock_request.seed = 999
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"max_tokens", "temperature", "top_p", "seed"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
# Overridden by request
|
|
|
|
|
assert comprehension_params.temperature == 0.7
|
|
|
|
|
assert comprehension_params.top_p == 0.85
|
|
|
|
|
assert comprehension_params.max_tokens == 200
|
|
|
|
|
assert comprehension_params.seed == 999
|
|
|
|
|
# Preserved from YAML (not in _OPENAI_SAMPLING_FIELDS)
|
|
|
|
|
assert comprehension_params.top_k == 1
|
|
|
|
|
assert comprehension_params.repetition_penalty == 1.05
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for _apply_request_overrides
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_request_overrides_clones_params(mock_engine_client, mock_request, default_comprehension_params):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that _apply_request_overrides returns a cloned object."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
result = serving_chat._apply_request_overrides(default_comprehension_params, mock_request)
|
|
|
|
|
|
|
|
|
|
assert result is not default_comprehension_params # Different object
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_request_overrides_preserves_defaults(mock_engine_client, mock_request, default_comprehension_params):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that _apply_request_overrides preserves defaults when request has None."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
result = serving_chat._apply_request_overrides(default_comprehension_params, mock_request)
|
|
|
|
|
|
|
|
|
|
assert result.temperature == 0.4
|
|
|
|
|
assert result.top_p == 0.9
|
|
|
|
|
assert result.seed == 42
|
|
|
|
|
assert result.top_k == 1 # YAML custom param
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_request_overrides_applies_values(mock_engine_client, mock_request, default_comprehension_params):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that _apply_request_overrides applies non-None request values."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
mock_request.temperature = 0.8
|
|
|
|
|
mock_request.seed = 123
|
2026-04-20 19:24:06 +08:00
|
|
|
mock_request.model_fields_set = {"temperature", "seed"}
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_comprehension_params, mock_request)
|
|
|
|
|
|
|
|
|
|
assert result.temperature == 0.8 # Overridden
|
|
|
|
|
assert result.seed == 123 # Overridden
|
|
|
|
|
assert result.top_p == 0.9 # Preserved from default
|
|
|
|
|
assert result.top_k == 1 # YAML custom param preserved
|
|
|
|
|
|
|
|
|
|
|
2026-04-16 22:40:34 +08:00
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for empty-list handling in _apply_request_overrides
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_empty_stop_list_preserves_default(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that request.stop=[] does NOT override YAML default stop words."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(temperature=0.5, stop=["<|im_end|>"])
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = [] # empty list — should be treated as "not set"
|
|
|
|
|
request.stop_token_ids = None
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = {"stop"}
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.stop == ["<|im_end|>"] # YAML default preserved
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_nonempty_stop_list_overrides_default(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that request.stop=["\\n"] overrides YAML default stop words."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(temperature=0.5, stop=["<|im_end|>"])
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = ["\n"] # non-empty list — should override
|
|
|
|
|
request.stop_token_ids = None
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = {"stop"}
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.stop == ["\n"] # Overridden by request
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_empty_stop_token_ids_preserves_default(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that request.stop_token_ids=[] does NOT override YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(temperature=0.5, stop_token_ids=[2, 3])
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = None
|
|
|
|
|
request.stop_token_ids = [] # empty list — should be treated as "not set"
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
|
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.stop_token_ids == [2, 3] # YAML default preserved
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_nonempty_stop_token_ids_overrides_default(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that request.stop_token_ids=[100] overrides YAML default."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(temperature=0.5, stop_token_ids=[2, 3])
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = None
|
|
|
|
|
request.stop_token_ids = [100] # non-empty list — should override
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = {"stop_token_ids"}
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.stop_token_ids == [100] # Overridden by request
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_mixed_empty_and_nonempty_lists(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test mixing empty and non-empty list fields with scalar fields."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(
|
|
|
|
|
temperature=0.4,
|
|
|
|
|
stop=["<|end|>"],
|
|
|
|
|
stop_token_ids=[2],
|
|
|
|
|
)
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = 0.9
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = [] # empty — should NOT override
|
|
|
|
|
request.stop_token_ids = [100, 200] # non-empty — SHOULD override
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = {"temperature", "stop", "stop_token_ids"}
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.temperature == 0.9 # Scalar override works
|
|
|
|
|
assert result.stop == ["<|end|>"] # Empty list did NOT override
|
|
|
|
|
assert result.stop_token_ids == [100, 200] # Non-empty list DID override
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_none_scalar_still_preserves_default(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Regression: ensure None scalar values still don't override defaults."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(temperature=0.5, max_tokens=100, seed=42)
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = None
|
|
|
|
|
request.stop_token_ids = None
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = set()
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.temperature == 0.5
|
|
|
|
|
assert result.max_tokens == 100
|
|
|
|
|
assert result.seed == 42
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_apply_overrides_both_lists_empty_preserves_defaults(mock_engine_client, mocker):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that both stop=[] and stop_token_ids=[] preserve YAML defaults."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
default_params = SamplingParams(
|
|
|
|
|
temperature=0.5,
|
|
|
|
|
stop=["<|end|>", "\\n"],
|
|
|
|
|
stop_token_ids=[2, 32000],
|
|
|
|
|
)
|
|
|
|
|
request = mocker.MagicMock()
|
|
|
|
|
request.temperature = None
|
|
|
|
|
request.top_p = None
|
|
|
|
|
request.top_k = None
|
|
|
|
|
request.max_tokens = None
|
|
|
|
|
request.min_tokens = None
|
|
|
|
|
request.seed = None
|
|
|
|
|
request.ignore_eos = None
|
|
|
|
|
request.stop = []
|
|
|
|
|
request.stop_token_ids = []
|
|
|
|
|
request.frequency_penalty = None
|
|
|
|
|
request.presence_penalty = None
|
2026-04-20 19:24:06 +08:00
|
|
|
request.model_fields_set = {"stop", "stop_token_ids"}
|
|
|
|
|
request.extra_body = {}
|
2026-04-16 22:40:34 +08:00
|
|
|
|
|
|
|
|
result = serving_chat._apply_request_overrides(default_params, request)
|
|
|
|
|
|
|
|
|
|
assert result.stop == ["<|end|>", "\\n"]
|
|
|
|
|
assert result.stop_token_ids == [2, 32000]
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_build_sampling_params_list_empty_stop_preserves_yaml(mock_engine_client, mock_request):
|
2026-04-16 22:40:34 +08:00
|
|
|
"""Test that empty stop list in request preserves YAML defaults via
|
|
|
|
|
_build_sampling_params_list_from_request."""
|
2026-08-20 15:05:05 -05:00
|
|
|
serving_chat = build_serving_chat(engine_client=mock_engine_client)
|
2026-04-16 22:40:34 +08:00
|
|
|
mock_request.stop = []
|
|
|
|
|
mock_request.stop_token_ids = []
|
|
|
|
|
|
|
|
|
|
result = serving_chat._build_sampling_params_list_from_request(mock_request)
|
|
|
|
|
|
|
|
|
|
comprehension_params = result[0]
|
|
|
|
|
# Empty lists should NOT override — YAML defaults are preserved
|
|
|
|
|
assert comprehension_params.stop == []
|
|
|
|
|
assert comprehension_params.stop_token_ids == []
|
|
|
|
|
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
def test_to_sampling_params_list_pads_missing_tail_stage_with_defaults(mocker: MockerFixture):
|
2026-06-21 22:08:26 +08:00
|
|
|
"""AURA callers may pass 3 semantic model params for a 4-stage engine pipeline."""
|
|
|
|
|
default_params = [
|
|
|
|
|
SamplingParams(max_tokens=10),
|
|
|
|
|
SamplingParams(max_tokens=20),
|
|
|
|
|
SamplingParams(max_tokens=30),
|
|
|
|
|
SamplingParams(max_tokens=40),
|
|
|
|
|
]
|
2026-08-20 15:05:05 -05:00
|
|
|
engine_client = mocker.MagicMock()
|
|
|
|
|
engine_client.stage_configs = [SimpleNamespace(stage_type="llm") for _ in range(4)]
|
|
|
|
|
engine_client.default_sampling_params_list = default_params
|
|
|
|
|
instance = build_serving_chat(engine_client=engine_client)
|
2026-06-21 22:08:26 +08:00
|
|
|
|
|
|
|
|
result = instance._to_sampling_params_list(
|
|
|
|
|
[
|
|
|
|
|
{"max_tokens": 1},
|
|
|
|
|
{"max_tokens": 2},
|
|
|
|
|
{"max_tokens": 3},
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert len(result) == 4
|
|
|
|
|
assert [params.max_tokens for params in result] == [1, 2, 3, 40]
|
|
|
|
|
assert result[3] is not default_params[3]
|
|
|
|
|
|
|
|
|
|
|
2025-12-24 21:39:01 +09:00
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for _get_comprehension_stage_index
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_comprehension_stage_index_finds_first_stage(mock_engine_client):
|
|
|
|
|
"""Test finding comprehension stage when it's at index 0."""
|
2026-08-20 15:05:05 -05:00
|
|
|
instance = build_serving_chat(engine_client=mock_engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
assert instance._get_comprehension_stage_index() == 0
|
|
|
|
|
|
|
|
|
|
|
2026-02-26 17:08:39 +08:00
|
|
|
def test_get_comprehension_stage_index_finds_second_stage(mocker: MockerFixture):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test finding comprehension stage when it's at index 1."""
|
2026-02-26 17:08:39 +08:00
|
|
|
other = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
other.is_comprehension = False
|
2026-02-26 17:08:39 +08:00
|
|
|
comprehension = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
comprehension.is_comprehension = True
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
engine_client = mocker.MagicMock()
|
|
|
|
|
engine_client.stage_configs = [other, comprehension]
|
|
|
|
|
instance = build_serving_chat(engine_client=engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
assert instance._get_comprehension_stage_index() == 1
|
|
|
|
|
|
|
|
|
|
|
2026-02-26 17:08:39 +08:00
|
|
|
def test_get_comprehension_stage_index_raises_when_not_found(mocker: MockerFixture):
|
2025-12-24 21:39:01 +09:00
|
|
|
"""Test that ValueError is raised when no comprehension stage exists."""
|
2026-02-26 17:08:39 +08:00
|
|
|
stage1 = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
stage1.is_comprehension = False
|
2026-02-26 17:08:39 +08:00
|
|
|
stage2 = mocker.MagicMock()
|
2025-12-24 21:39:01 +09:00
|
|
|
stage2.is_comprehension = False
|
|
|
|
|
|
2026-08-20 15:05:05 -05:00
|
|
|
engine_client = mocker.MagicMock()
|
|
|
|
|
engine_client.stage_configs = [stage1, stage2]
|
|
|
|
|
instance = build_serving_chat(engine_client=engine_client)
|
2025-12-24 21:39:01 +09:00
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="No comprehension stage"):
|
|
|
|
|
instance._get_comprehension_stage_index()
|
2026-04-20 19:24:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for _resolve_height_width_from_extra_body
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestResolveHeightWidth:
|
|
|
|
|
def test_explicit_height_width(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({"height": 512, "width": 768})
|
|
|
|
|
assert h == 512
|
|
|
|
|
assert w == 768
|
|
|
|
|
|
|
|
|
|
def test_size_string(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({"size": "768x512"})
|
|
|
|
|
assert w == 768
|
|
|
|
|
assert h == 512
|
|
|
|
|
|
|
|
|
|
def test_size_string_uppercase(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({"size": "768X512"})
|
|
|
|
|
assert w == 768
|
|
|
|
|
assert h == 512
|
|
|
|
|
|
|
|
|
|
def test_size_fallback_when_height_missing(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({"size": "512x512", "width": 1024})
|
|
|
|
|
# height is None -> size fallback fires and sets BOTH width and height
|
|
|
|
|
assert h == 512
|
|
|
|
|
assert w == 512
|
|
|
|
|
|
|
|
|
|
def test_empty_extra_body(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({})
|
|
|
|
|
assert h is None
|
|
|
|
|
assert w is None
|
|
|
|
|
|
|
|
|
|
def test_invalid_size_format_ignored(self):
|
|
|
|
|
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
|
|
|
|
|
|
|
|
|
|
h, w = OmniOpenAIServingChat._resolve_height_width_from_extra_body({"size": "invalid"})
|
|
|
|
|
assert h is None
|
|
|
|
|
assert w is None
|