diff --git a/tensorrt_llm/sampling_params.py b/tensorrt_llm/sampling_params.py index 5a1c74fa4d1a..34c92979fe77 100644 --- a/tensorrt_llm/sampling_params.py +++ b/tensorrt_llm/sampling_params.py @@ -372,13 +372,18 @@ def _validate(self): For instance, while the greedy decoding with n > 1 is capable in the Executor class of C++ runtime, the LLM API disallows such combination. """ - if self.top_p is not None and (self.top_p < 0 or self.top_p > 1): + # These bounds are written as negated range checks rather than as + # `value < low or value > high`, so that NaN is rejected too: every + # comparison against NaN is False, which lets it slip through the + # positive form. The top_p_decay / top_p_min checks below already use + # this form. + if self.top_p is not None and not 0 <= self.top_p <= 1: raise ValueError(f"require 0 <= top_p <= 1, got top_p={self.top_p}") if self.top_k is not None and self.top_k < 0: raise ValueError(f"require top_k >= 0, got top_k={self.top_k}") - if self.min_p is not None and (self.min_p < 0 or self.min_p > 1): + if self.min_p is not None and not 0 <= self.min_p <= 1: raise ValueError(f"require 0 <= min_p <= 1, got min_p={self.min_p}") - if self.temperature is not None and self.temperature < 0: + if self.temperature is not None and not self.temperature >= 0: raise ValueError(f"require temperature >= 0, got temperature={self.temperature}") # Top-p decay param ranges mirror the hard checks in the diff --git a/tests/integration/test_lists/test-db/l0_cpu.yml b/tests/integration/test_lists/test-db/l0_cpu.yml index 3bb62ffd3b89..5a1a08f2e19b 100644 --- a/tests/integration/test_lists/test-db/l0_cpu.yml +++ b/tests/integration/test_lists/test-db/l0_cpu.yml @@ -41,6 +41,7 @@ l0_cpu: - unittest/llmapi/test_mpi_session.py ISOLATION - unittest/llmapi/test_reasoning_parser.py - unittest/llmapi/test_request_priority.py + - unittest/llmapi/test_sampling_params.py - unittest/llmapi/test_serialization.py - unittest/llmapi/test_utils.py - unittest/others/test_http_utils_fail_fast.py diff --git a/tests/unittest/llmapi/test_sampling_params.py b/tests/unittest/llmapi/test_sampling_params.py index 8c5d2a7b5d3f..75d2a6c45e4d 100644 --- a/tests/unittest/llmapi/test_sampling_params.py +++ b/tests/unittest/llmapi/test_sampling_params.py @@ -117,6 +117,44 @@ def test_completion_logprobs_assignment_revalidates(): request.to_sampling_params(backend="pytorch") +@pytest.mark.parametrize("field", ["top_p", "min_p", "temperature"]) +def test_sampling_params_rejects_nan(field): + with pytest.raises(ValueError, match=field): + SamplingParams(**{field: float("nan")}) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("top_p", -0.1), + ("top_p", 1.1), + ("min_p", -0.1), + ("min_p", 1.1), + ("temperature", -1.0), + ], +) +def test_sampling_params_rejects_out_of_range(field, value): + with pytest.raises(ValueError, match=field): + SamplingParams(**{field: value}) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("top_p", 0.0), + ("top_p", 0.9), + ("top_p", 1.0), + ("min_p", 0.0), + ("min_p", 0.5), + ("min_p", 1.0), + ("temperature", 0.0), + ("temperature", 1.0), + ], +) +def test_sampling_params_accepts_in_range_values(field, value): + assert getattr(SamplingParams(**{field: value}), field) == value + + @pytest.mark.parametrize("value", [None, -1]) def test_thinking_token_budget_unlimited_values(value): assert SamplingParams(thinking_token_budget=value).thinking_token_budget is None