From 2dd887426e28dd7dfc96465acc45e06e849f3d44 Mon Sep 17 00:00:00 2001 From: dingdangmao <2756823972@qq.com> Date: Thu, 8 Oct 2026 09:03:08 +0800 Subject: [PATCH] [Bugfix][Frontend] Do not modify caller params in LLM.score() (#59959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叮当猫 <2756823972@qq.com> Signed-off-by: apex-mochen <2756823972@qq.com> Co-authored-by: DeepSeek Harness Co-authored-by: Taneem Ibrahim Co-authored-by: Codex --- .../entrypoints/pooling/test_io_processor.py | 37 +++++++++++++++++++ vllm/entrypoints/pooling/offline.py | 2 + 2 files changed, 39 insertions(+) diff --git a/tests/entrypoints/pooling/test_io_processor.py b/tests/entrypoints/pooling/test_io_processor.py index 9d2207fdf1..79bddc0ffe 100644 --- a/tests/entrypoints/pooling/test_io_processor.py +++ b/tests/entrypoints/pooling/test_io_processor.py @@ -7,6 +7,8 @@ import pytest from vllm import PoolingParams from vllm.entrypoints.pooling.base.io_processor import PoolingIOProcessor +from vllm.entrypoints.pooling.offline import PoolingOfflineMixin +from vllm.entrypoints.pooling.scoring.io_processor import CrossEncoderIOProcessor from vllm.entrypoints.pooling.typing import OfflineEncodeInputsContext from vllm.exceptions import VLLMValidationError from vllm.renderers import TokenizeParams @@ -221,3 +223,38 @@ def test_qwen3_reranker_no_warning_when_template_provided(monkeypatch): ) mock_logger.warning.assert_not_called() + + +def _make_score_llm(monkeypatch) -> PoolingOfflineMixin: + """A scoring LLM with no model behind it. + + Input validation runs for real; only the rendering, engine and + post-processing boundaries are stubbed out. The pooling task is filled in + before any of them, so the mutation is observable without inference. + """ + proc = CrossEncoderIOProcessor.__new__(CrossEncoderIOProcessor) + proc.is_multimodal_model = False + proc.architecture = "CrossEncoder" + monkeypatch.setattr(proc, "get_request_factory_offline", lambda ctx: (None, 0)) + monkeypatch.setattr(proc, "post_process_offline", lambda ctx: []) + + llm = PoolingOfflineMixin.__new__(PoolingOfflineMixin) + llm.runner_type = "pooling" + llm.pooling_task = "classify" # SCORE_TYPE_MAP -> "cross-encoder" + llm.model_config = SimpleNamespace(hf_config=SimpleNamespace(num_labels=1)) + llm.pooling_io_processors = {"cross-encoder": proc} + monkeypatch.setattr(llm, "_run_tiling_engine", lambda *args, **kwargs: []) + return llm + + +def test_score_leaves_caller_pooling_params_untouched(monkeypatch): + llm = _make_score_llm(monkeypatch) + + params = PoolingParams() + llm.score("query", "doc", pooling_params=params) + + assert params.task is None, ( + "LLM.score() wrote the model's own pooling task into the caller's " + "PoolingParams; reusing that object with another pooling model now " + "fails with 'You cannot overwrite ...' before any inference runs." + ) diff --git a/vllm/entrypoints/pooling/offline.py b/vllm/entrypoints/pooling/offline.py index 0a0304e7a4..27388f1ff0 100644 --- a/vllm/entrypoints/pooling/offline.py +++ b/vllm/entrypoints/pooling/offline.py @@ -362,6 +362,8 @@ class PoolingOfflineMixin(OfflineInferenceMixin): assert isinstance(pooling_params, PoolingParams) pooling_task = io_processor.pooling_task + # Clone to avoid modifying the caller's pooling parameters. + pooling_params = pooling_params.clone() if pooling_params.task is None: pooling_params.task = pooling_task elif pooling_params.task != pooling_task: