From e6fc81bc7892f2f58c0e347a701fc060ceef44bb Mon Sep 17 00:00:00 2001 From: Tommy Date: Wed, 7 Oct 2026 03:32:00 -0400 Subject: [PATCH] [Bugfix][Scheduler] Let pooling chunked prefill use full context (#48039) Signed-off-by: Tommy Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: OpenAI Codex Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> --- tests/v1/core/test_scheduler.py | 19 +++++++++++++++++++ tests/v1/core/utils.py | 3 +++ vllm/v1/core/sched/scheduler.py | 10 ++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/v1/core/test_scheduler.py b/tests/v1/core/test_scheduler.py index f78db1917d..7a8a1a3e4e 100644 --- a/tests/v1/core/test_scheduler.py +++ b/tests/v1/core/test_scheduler.py @@ -35,6 +35,7 @@ from vllm.multimodal.inputs import ( MultiModalKwargsItem, PlaceholderRange, ) +from vllm.pooling_params import PoolingParams from vllm.sampling_params import SamplingParams, StructuredOutputsParams from vllm.utils.hashing import sha256 from vllm.v1.core.encoder_cache_manager import EncoderCacheManager @@ -279,6 +280,24 @@ def test_schedule(enable_prefix_caching: bool, prompt_logprobs: int | None): assert scheduler.running[i] == request +def test_pooling_chunked_prefill_can_finish_at_max_model_len(): + scheduler = create_scheduler( + max_num_seqs=1, max_model_len=8, max_num_batched_tokens=4, runner="pooling" + ) + assert scheduler.max_model_len == 8 + request = Request( + request_id="pool", + prompt_token_ids=[1] * 8, + sampling_params=None, + pooling_params=PoolingParams(task="embed"), + ) + scheduler.add_request(request) + + scheduler.schedule() + # The final chunk must reach max_model_len, not stop one token short. + assert scheduler.schedule().num_scheduled_tokens == {"pool": 4} + + def test_scheduler_stats_route_to_existing_output_client(): scheduler = create_scheduler() request = create_requests(num_requests=1)[0] diff --git a/tests/v1/core/utils.py b/tests/v1/core/utils.py index 49aaca3d7c..b5e1bf8932 100644 --- a/tests/v1/core/utils.py +++ b/tests/v1/core/utils.py @@ -18,6 +18,7 @@ from vllm.config import ( SpeculativeConfig, VllmConfig, ) +from vllm.config.model import RunnerOption from vllm.config.scheduler import SchedulerPolicy from vllm.multimodal.inputs import ( MultiModalFeatureSpec, @@ -78,6 +79,7 @@ def create_scheduler( ec_role: str | None = None, use_v2_model_runner: bool | None = None, kv_cache_spec: KVCacheSpec | None = None, + runner: RunnerOption = "auto", per_request_spec_decode_metrics: str = "none", scheduling_policy: SchedulerPolicy = "fcfs", diffusion_canvas_length: int | None = None, @@ -109,6 +111,7 @@ def create_scheduler( # The scheduler reads model_config.max_model_len, not the # SchedulerConfig one, so both must agree. max_model_len=max_model_len, + runner=runner, ) if mm_encoder_only: model_config.multimodal_config.mm_encoder_only = True diff --git a/vllm/v1/core/sched/scheduler.py b/vllm/v1/core/sched/scheduler.py index 8bdf91da86..f908a31a1b 100644 --- a/vllm/v1/core/sched/scheduler.py +++ b/vllm/v1/core/sched/scheduler.py @@ -147,9 +147,15 @@ class Scheduler(SchedulerInterface): self.kv_events_config is not None and self.kv_events_config.enable_kv_cache_events ) - # Diffusion models may not sample any tokens for a denoising step. + # Pooling and diffusion models do not append a sampled token after + # prefill, so they should not reserve one context slot while scheduling. self.num_sampled_tokens_per_step = ( - 1 if not vllm_config.model_config.is_diffusion else 0 + 0 + if ( + vllm_config.model_config.runner_type == "pooling" + or vllm_config.model_config.is_diffusion + ) + else 1 ) # Create KVConnector for the Scheduler. Note that each Worker