| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import pytest |
| 5 | |
| 6 | from vllm import SamplingParams |
| 7 | from vllm.exceptions import VLLMValidationError |
| 8 | from vllm.sampling_params import StructuredOutputsParams |
| 9 | |
| 10 | pytestmark = pytest.mark.skip_global_cleanup |
| 11 | |
| 12 | # --------------------------------------------------------------------------- |
| 13 | # SamplingParams field |
| 14 | # --------------------------------------------------------------------------- |
| 15 | |
| 16 | |
| 17 | def test_sampling_params_trace_field_defaults_to_none(): |
| 18 | params = SamplingParams(max_tokens=10) |
| 19 | assert params.trace_decode_token_ids is None |
| 20 | |
| 21 | |
| 22 | def test_sampling_params_trace_field_accepts_list(): |
| 23 | ids = [100, 200, 300] |
| 24 | params = SamplingParams(trace_decode_token_ids=ids) |
| 25 | assert params.trace_decode_token_ids == ids |
| 26 | |
| 27 | |
| 28 | def test_sampling_params_trace_field_preserved_by_clone(): |
| 29 | ids = [1, 2, 3] |
| 30 | params = SamplingParams(trace_decode_token_ids=ids) |
| 31 | cloned = params.clone() |
| 32 | assert cloned.trace_decode_token_ids == ids |
| 33 | assert cloned.trace_decode_token_ids is not params.trace_decode_token_ids |
| 34 | |
| 35 | |
| 36 | def test_sampling_params_trace_field_rejects_empty_list(): |
| 37 | params = SamplingParams(trace_decode_token_ids=[]) |
| 38 | with pytest.raises(ValueError, match="non-empty"): |
| 39 | params._validate_trace_replay( |
| 40 | _make_model_config(vocab_size=100), speculative_config=None |
| 41 | ) |
| 42 | |
| 43 | |
| 44 | def test_sampling_params_trace_field_rejects_prompt_scoring(): |
| 45 | params = SamplingParams(trace_decode_token_ids=[1], prompt_logprob_token_ids=[2, 3]) |
| 46 | with pytest.raises(ValueError, match="prompt_logprob_token_ids"): |
| 47 | params._validate_trace_replay( |
| 48 | _make_model_config(vocab_size=100), speculative_config=None |
| 49 | ) |
| 50 | |
| 51 | |
| 52 | def test_sampling_params_trace_field_requires_single_output(): |
| 53 | params = SamplingParams(n=2, trace_decode_token_ids=[1]) |
| 54 | with pytest.raises(ValueError, match="requires n=1"): |
| 55 | params._validate_trace_replay( |
| 56 | _make_model_config(vocab_size=100), speculative_config=None |
| 57 | ) |
| 58 | |
| 59 | |
| 60 | @pytest.mark.parametrize("invalid_ids", [[-1, 5], [1, "2"]]) |
| 61 | def test_sampling_params_trace_field_rejects_invalid_token_ids(invalid_ids): |
| 62 | params = SamplingParams(trace_decode_token_ids=invalid_ids) |
| 63 | with pytest.raises(ValueError, match="non-negative integers"): |
| 64 | params._validate_trace_replay( |
| 65 | _make_model_config(vocab_size=100), speculative_config=None |
| 66 | ) |
| 67 | |
| 68 | |
| 69 | def _make_model_config(vocab_size: int): |
| 70 | from unittest.mock import Mock |
| 71 | |
| 72 | model_config = Mock() |
| 73 | model_config.get_vocab_size = lambda: vocab_size |
| 74 | return model_config |
| 75 | |
| 76 | |
| 77 | def test_validate_trace_replay_accepts_in_vocab(): |
| 78 | params = SamplingParams(trace_decode_token_ids=[0, 50, 99]) |
| 79 | # Should not raise. |
| 80 | params._validate_trace_replay( |
| 81 | _make_model_config(vocab_size=100), speculative_config=None |
| 82 | ) |
| 83 | |
| 84 | |
| 85 | def test_validate_trace_replay_rejects_out_of_vocab(): |
| 86 | # The non-negative check passes at construction, but the token id exceeds |
| 87 | # the vocabulary; verify() must reject it before it reaches the sampler. |
| 88 | params = SamplingParams(trace_decode_token_ids=[0, 100]) |
| 89 | with pytest.raises(VLLMValidationError, match="out-of-vocab"): |
| 90 | params._validate_trace_replay( |
| 91 | _make_model_config(vocab_size=100), speculative_config=None |
| 92 | ) |
| 93 | |
| 94 | |
| 95 | def test_validate_trace_replay_noop_when_unset(): |
| 96 | params = SamplingParams(max_tokens=4) |
| 97 | # Should not raise when the field is unset. |
| 98 | params._validate_trace_replay( |
| 99 | _make_model_config(vocab_size=100), speculative_config=None |
| 100 | ) |
| 101 | |
| 102 | |
| 103 | def test_trace_decode_token_ids_rejects_speculative_decoding(): |
| 104 | params = SamplingParams(trace_decode_token_ids=[1]) |
| 105 | with pytest.raises(ValueError, match="not supported with speculative decoding"): |
| 106 | params._validate_trace_replay( |
| 107 | _make_model_config(vocab_size=100), |
| 108 | speculative_config=object(), |
| 109 | ) |
| 110 | |
| 111 | |
| 112 | def test_trace_decode_token_ids_rejects_structured_outputs(): |
| 113 | params = SamplingParams( |
| 114 | trace_decode_token_ids=[1], |
| 115 | structured_outputs=StructuredOutputsParams(json_object=True), |
| 116 | ) |
| 117 | with pytest.raises(ValueError, match="not supported with structured outputs"): |
| 118 | params._validate_trace_replay( |
| 119 | _make_model_config(vocab_size=100), speculative_config=None |
| 120 | ) |