# 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. """ import pytest from pytest_mock import MockerFixture from vllm.sampling_params import SamplingParams pytestmark = [pytest.mark.core_model, pytest.mark.cpu] @pytest.fixture def mock_comprehension_stage(mocker: MockerFixture): """Create a mock comprehension stage with is_comprehension=True.""" stage = mocker.MagicMock() stage.is_comprehension = True stage.model_stage = "comprehension" return stage @pytest.fixture def mock_other_stage(mocker: MockerFixture): """Create a mock non-comprehension stage.""" stage = mocker.MagicMock() 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, max_tokens=4353, 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 def mock_engine_client( mock_comprehension_stage, mock_other_stage, default_comprehension_params, default_other_params, mocker: MockerFixture, ): """Create mock engine client with stage_configs and default_sampling_params_list.""" engine_client = mocker.MagicMock() engine_client.stage_configs = [mock_comprehension_stage, mock_other_stage] engine_client.default_sampling_params_list = [ default_comprehension_params, default_other_params, ] return engine_client @pytest.fixture def serving_chat(mock_engine_client): """Create OmniOpenAIServingChat instance with mocked dependencies.""" from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat # Create instance without calling __init__ instance = object.__new__(OmniOpenAIServingChat) instance.engine_client = mock_engine_client return instance @pytest.fixture def mock_request(mocker: MockerFixture): """Create a mock request with all OpenAI sampling params set to None.""" request = mocker.MagicMock() # OpenAI standard sampling fields 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 # 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 = {} 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", "top_k", "max_tokens", "min_tokens", "seed", "ignore_eos", "stop", "stop_token_ids", "frequency_penalty", "presence_penalty", } assert OmniOpenAIServingChat._OPENAI_SAMPLING_FIELDS == expected_fields # ============================================================================= # Tests for _build_sampling_params_list_from_request # ============================================================================= def test_preserves_yaml_defaults_when_no_request_params(serving_chat, mock_request): """Test that YAML defaults are preserved when request has no params.""" 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 assert comprehension_params.max_tokens == 4353 assert comprehension_params.seed == 42 assert comprehension_params.repetition_penalty == 1.05 # YAML custom param preserved def test_request_temperature_overrides_yaml_default(serving_chat, mock_request): """Test that request temperature overrides YAML default.""" mock_request.temperature = 0.8 mock_request.model_fields_set = {"temperature"} 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 def test_request_top_p_overrides_yaml_default(serving_chat, mock_request): """Test that request top_p overrides YAML default.""" mock_request.top_p = 0.95 mock_request.model_fields_set = {"top_p"} 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 def test_request_max_tokens_overrides_yaml_default(serving_chat, mock_request): """Test that request max_tokens overrides YAML default.""" mock_request.max_tokens = 100 mock_request.model_fields_set = {"max_tokens"} result = serving_chat._build_sampling_params_list_from_request(mock_request) assert result[0].max_tokens == 100 def test_max_tokens_uses_yaml_default_when_not_specified(serving_chat, mock_request): """Test that max_tokens falls back to YAML default when not in request.""" result = serving_chat._build_sampling_params_list_from_request(mock_request) assert result[0].max_tokens == 4353 def test_request_seed_overrides_yaml_default(serving_chat, mock_request): """Test that request seed overrides YAML default.""" mock_request.seed = 123 mock_request.model_fields_set = {"seed"} 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 def test_request_frequency_penalty_overrides(serving_chat, mock_request): """Test that request frequency_penalty is applied.""" mock_request.frequency_penalty = 0.5 mock_request.model_fields_set = {"frequency_penalty"} result = serving_chat._build_sampling_params_list_from_request(mock_request) assert result[0].frequency_penalty == 0.5 def test_request_presence_penalty_overrides(serving_chat, mock_request): """Test that request presence_penalty is applied.""" mock_request.presence_penalty = 0.3 mock_request.model_fields_set = {"presence_penalty"} result = serving_chat._build_sampling_params_list_from_request(mock_request) assert result[0].presence_penalty == 0.3 def test_non_comprehension_stages_use_cloned_defaults(serving_chat, mock_request): """Test that non-comprehension stages always use cloned YAML defaults.""" 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 def test_multiple_params_override_together(serving_chat, mock_request): """Test that multiple request params can override together.""" mock_request.max_tokens = 200 mock_request.temperature = 0.7 mock_request.top_p = 0.85 mock_request.seed = 999 mock_request.model_fields_set = {"max_tokens", "temperature", "top_p", "seed"} 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 # ============================================================================= def test_apply_request_overrides_clones_params(serving_chat, mock_request, default_comprehension_params): """Test that _apply_request_overrides returns a cloned object.""" result = serving_chat._apply_request_overrides(default_comprehension_params, mock_request) assert result is not default_comprehension_params # Different object def test_apply_request_overrides_preserves_defaults(serving_chat, mock_request, default_comprehension_params): """Test that _apply_request_overrides preserves defaults when request has None.""" 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 def test_apply_request_overrides_applies_values(serving_chat, mock_request, default_comprehension_params): """Test that _apply_request_overrides applies non-None request values.""" mock_request.temperature = 0.8 mock_request.seed = 123 mock_request.model_fields_set = {"temperature", "seed"} 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 # ============================================================================= # Tests for empty-list handling in _apply_request_overrides # ============================================================================= def test_apply_overrides_empty_stop_list_preserves_default(serving_chat, mocker): """Test that request.stop=[] does NOT override YAML default stop words.""" 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 request.model_fields_set = {"stop"} request.extra_body = {} result = serving_chat._apply_request_overrides(default_params, request) assert result.stop == ["<|im_end|>"] # YAML default preserved def test_apply_overrides_nonempty_stop_list_overrides_default(serving_chat, mocker): """Test that request.stop=["\\n"] overrides YAML default stop words.""" 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 request.model_fields_set = {"stop"} request.extra_body = {} result = serving_chat._apply_request_overrides(default_params, request) assert result.stop == ["\n"] # Overridden by request def test_apply_overrides_empty_stop_token_ids_preserves_default(serving_chat, mocker): """Test that request.stop_token_ids=[] does NOT override YAML default.""" 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 def test_apply_overrides_nonempty_stop_token_ids_overrides_default(serving_chat, mocker): """Test that request.stop_token_ids=[100] overrides YAML default.""" 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 request.model_fields_set = {"stop_token_ids"} request.extra_body = {} result = serving_chat._apply_request_overrides(default_params, request) assert result.stop_token_ids == [100] # Overridden by request def test_apply_overrides_mixed_empty_and_nonempty_lists(serving_chat, mocker): """Test mixing empty and non-empty list fields with scalar fields.""" 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 request.model_fields_set = {"temperature", "stop", "stop_token_ids"} request.extra_body = {} 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 def test_apply_overrides_none_scalar_still_preserves_default(serving_chat, mocker): """Regression: ensure None scalar values still don't override defaults.""" 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 request.model_fields_set = set() request.extra_body = {} result = serving_chat._apply_request_overrides(default_params, request) assert result.temperature == 0.5 assert result.max_tokens == 100 assert result.seed == 42 def test_apply_overrides_both_lists_empty_preserves_defaults(serving_chat, mocker): """Test that both stop=[] and stop_token_ids=[] preserve YAML defaults.""" 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 request.model_fields_set = {"stop", "stop_token_ids"} request.extra_body = {} result = serving_chat._apply_request_overrides(default_params, request) assert result.stop == ["<|end|>", "\\n"] assert result.stop_token_ids == [2, 32000] def test_build_sampling_params_list_empty_stop_preserves_yaml(serving_chat, mock_request): """Test that empty stop list in request preserves YAML defaults via _build_sampling_params_list_from_request.""" 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 == [] # ============================================================================= # 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.""" from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat instance = object.__new__(OmniOpenAIServingChat) instance.engine_client = mock_engine_client assert instance._get_comprehension_stage_index() == 0 def test_get_comprehension_stage_index_finds_second_stage(mocker: MockerFixture): """Test finding comprehension stage when it's at index 1.""" from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat instance = object.__new__(OmniOpenAIServingChat) other = mocker.MagicMock() other.is_comprehension = False comprehension = mocker.MagicMock() comprehension.is_comprehension = True instance.engine_client = mocker.MagicMock() instance.engine_client.stage_configs = [other, comprehension] assert instance._get_comprehension_stage_index() == 1 def test_get_comprehension_stage_index_raises_when_not_found(mocker: MockerFixture): """Test that ValueError is raised when no comprehension stage exists.""" from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat instance = object.__new__(OmniOpenAIServingChat) stage1 = mocker.MagicMock() stage1.is_comprehension = False stage2 = mocker.MagicMock() stage2.is_comprehension = False instance.engine_client = mocker.MagicMock() instance.engine_client.stage_configs = [stage1, stage2] with pytest.raises(ValueError, match="No comprehension stage"): instance._get_comprehension_stage_index() # ============================================================================= # 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 # ============================================================================= # Tests for _apply_request_overrides with GLM-Image (target_h/w injection) # ============================================================================= class TestApplyRequestOverridesGLMImage: """Test target_h/w injection for GLM-Image AR stage. max_tokens is NOT computed dynamically — it comes from the deploy YAML default (e.g. 4353). _apply_request_overrides only injects target_h/w into extra_args so the model can build M-RoPE position grids. """ @pytest.fixture def glm_serving_chat(self, mock_engine_client, mocker: MockerFixture): from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat instance = object.__new__(OmniOpenAIServingChat) instance.engine_client = mock_engine_client instance._extract_diffusion_prompt_and_images_from_messages = mocker.MagicMock(return_value=("a cat", [])) return instance @pytest.fixture def glm_request(self, mocker: MockerFixture): req = mocker.MagicMock() req.temperature = None req.top_p = None req.top_k = None req.max_tokens = None req.min_tokens = None req.seed = None req.ignore_eos = None req.stop = None req.stop_token_ids = None req.frequency_penalty = None req.presence_penalty = None req.extra_body = {"height": 1024, "width": 1024} req.model_fields_set = set() return req def test_t2i_injects_target_h_w(self, glm_serving_chat, glm_request, default_comprehension_params): """t2i mode: target_h/w injected into extra_args, max_tokens unchanged.""" result = glm_serving_chat._apply_request_overrides(default_comprehension_params, glm_request) assert result.extra_args["target_h"] == 1024 assert result.extra_args["target_w"] == 1024 # max_tokens stays at YAML default (not dynamically computed) assert result.max_tokens == 4353 def test_i2i_injects_target_h_w( self, glm_serving_chat, glm_request, default_comprehension_params, mocker: MockerFixture ): """i2i mode: target_h/w injected, max_tokens unchanged.""" glm_serving_chat._extract_diffusion_prompt_and_images_from_messages = mocker.MagicMock( return_value=("edit this", ["fake_image"]) ) result = glm_serving_chat._apply_request_overrides(default_comprehension_params, glm_request) assert result.extra_args["target_h"] == 1024 assert result.extra_args["target_w"] == 1024 # max_tokens stays at YAML default regardless of t2i/i2i assert result.max_tokens == 4353 def test_user_max_tokens_preserved(self, glm_serving_chat, glm_request, default_comprehension_params): """User-provided max_tokens is respected (not overridden by dynamic computation).""" glm_request.max_tokens = 500 glm_request.model_fields_set = {"max_tokens"} result = glm_serving_chat._apply_request_overrides(default_comprehension_params, glm_request) assert result.max_tokens == 500 assert result.extra_args["target_h"] == 1024 assert result.extra_args["target_w"] == 1024 def test_no_height_width_preserves_default( self, glm_serving_chat, mocker: MockerFixture, default_comprehension_params ): """When no height/width in extra_body, keep YAML default max_tokens, no target_h/w.""" req = mocker.MagicMock() req.temperature = None req.top_p = None req.top_k = None req.max_tokens = None req.min_tokens = None req.seed = None req.ignore_eos = None req.stop = None req.stop_token_ids = None req.frequency_penalty = None req.presence_penalty = None req.extra_body = {} req.model_fields_set = set() result = glm_serving_chat._apply_request_overrides(default_comprehension_params, req) assert result.max_tokens == 4353 # YAML default # No target_h/w injected when dimensions not provided assert not result.extra_args or "target_h" not in (result.extra_args or {}) def test_size_string_parsed_for_glm_image( self, glm_serving_chat, mocker: MockerFixture, default_comprehension_params ): """'size' in extra_body is parsed as fallback for height/width.""" req = mocker.MagicMock() req.temperature = None req.top_p = None req.top_k = None req.max_tokens = None req.min_tokens = None req.seed = None req.ignore_eos = None req.stop = None req.stop_token_ids = None req.frequency_penalty = None req.presence_penalty = None req.extra_body = {"size": "512x512"} req.model_fields_set = set() result = glm_serving_chat._apply_request_overrides(default_comprehension_params, req) assert result.extra_args["target_h"] == 512 assert result.extra_args["target_w"] == 512 # max_tokens stays at YAML default (not dynamically computed) assert result.max_tokens == 4353