A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 18:30 UTC
[V1] Scheduler Refactoring [1/N] - Add Scheduler Interface (#15250)
Signed-off-by: Woosuk Kwon <woosuk.kwon@berkeley.edu> Co-authored-by: Cody Yu <hao.yu.cody@gmail.com> Co-authored-by: Nick Hill <nhill@redhat.com>
Woosuk Kwon committed
Mar 20, 2025 at 17:50 UTC
0c6f5023c390075e842bb7c70bb8f5aa433c584c
17 files changed
+182
-45
tests/plugins_tests/test_scheduler_plugins.py
+1
-1
@@ -6,7 +6,7 @@ from vllm.core.scheduler import Scheduler
6
from vllm.engine.arg_utils import EngineArgs
7
from vllm.engine.llm_engine import LLMEngine
8
from vllm.sampling_params import SamplingParams
9
-from vllm.v1.core.scheduler import Scheduler as V1Scheduler
9
+from vllm.v1.core.sched.scheduler import Scheduler as V1Scheduler
10
from vllm.v1.engine.llm_engine import LLMEngine as V1LLMEngine
11
12
tests/v1/core/test_scheduler.py
+2
-1
@@ -6,7 +6,8 @@ import pytest
6
from vllm.config import CacheConfig, ModelConfig, SchedulerConfig, VllmConfig
7
from vllm.multimodal.inputs import MultiModalKwargs, PlaceholderRange
8
from vllm.sampling_params import SamplingParams
9
-from vllm.v1.core.scheduler import Scheduler, SchedulerOutput
9
+from vllm.v1.core.sched.output import SchedulerOutput
10
+from vllm.v1.core.sched.scheduler import Scheduler
11
from vllm.v1.outputs import ModelRunnerOutput
12
from vllm.v1.request import Request, RequestStatus
13
from vllm.v1.structured_output import StructuredOutputManager
tests/v1/worker/test_gpu_model_runner.py
+2
-2
@@ -3,8 +3,8 @@ import pytest
3
4
from vllm.config import CacheConfig, ModelConfig, SchedulerConfig, VllmConfig
5
from vllm.sampling_params import SamplingParams
6
-from vllm.v1.core.scheduler_output import (CachedRequestData, NewRequestData,
7
- SchedulerOutput)
6
+from vllm.v1.core.sched.output import (CachedRequestData, NewRequestData,
7
+ SchedulerOutput)
8
from vllm.v1.sample.metadata import SamplingMetadata
9
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
10
vllm/engine/arg_utils.py
+1
-1
@@ -1695,7 +1695,7 @@ class EngineArgs:
1695
# V1 should use the new scheduler by default.
1696
# Swap it only if this arg is set to the original V0 default
1697
if self.scheduler_cls == EngineArgs.scheduler_cls:
1698
- self.scheduler_cls = "vllm.v1.core.scheduler.Scheduler"
1698
+ self.scheduler_cls = "vllm.v1.core.sched.scheduler.Scheduler"
1699
1700
# When no user override, set the default values based on the usage
1701
# context.
vllm/executor/ray_utils.py
+1
-1
@@ -17,7 +17,7 @@ from vllm.utils import get_ip
17
from vllm.worker.worker_base import WorkerWrapperBase
18
19
if TYPE_CHECKING:
20
- from vllm.v1.core.scheduler import SchedulerOutput
20
+ from vllm.v1.core.sched.output import SchedulerOutput
21
from vllm.v1.outputs import ModelRunnerOutput
22
23
logger = init_logger(__name__)
vllm/v1/attention/backends/flash_attn.py
+1
-1
@@ -17,7 +17,7 @@ from vllm.platforms import current_platform
17
from vllm.utils import cdiv
18
19
if TYPE_CHECKING:
20
- from vllm.v1.core.scheduler_output import SchedulerOutput
20
+ from vllm.v1.core.sched.output import SchedulerOutput
21
from vllm.v1.worker.gpu_input_batch import InputBatch
22
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
23
vllm/v1/attention/backends/mla/common.py
+1
-1
@@ -212,7 +212,7 @@ except ImportError:
212
from flash_attn import flash_attn_varlen_func
213
214
if TYPE_CHECKING:
215
- from vllm.v1.core.scheduler_output import SchedulerOutput
215
+ from vllm.v1.core.sched.output import SchedulerOutput
216
from vllm.v1.worker.gpu_input_batch import InputBatch
217
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
218
vllm/v1/core/sched/__init__.py
vllm/v1/core/sched/interface.py
new
+139
@@ -0,0 +1,139 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+from abc import ABC, abstractmethod
3
+from collections.abc import Iterable
4
+from typing import TYPE_CHECKING, Optional, Union
5
+
6
+if TYPE_CHECKING:
7
+ from vllm.v1.core.sched.output import SchedulerOutput
8
+ from vllm.v1.engine import EngineCoreOutputs
9
+ from vllm.v1.metrics.stats import SchedulerStats
10
+ from vllm.v1.outputs import ModelRunnerOutput
11
+ from vllm.v1.request import Request, RequestStatus
12
+
13
+
14
+class SchedulerInterface(ABC):
15
+
16
+ @abstractmethod
17
+ def schedule(self) -> "SchedulerOutput":
18
+ """Schedule the requests to process in this scheduling step.
19
+
20
+ The scheduling decision is made at the iteration level. Each scheduling
21
+ step corresponds to a single forward pass of the model. Therefore, this
22
+ method is called repeatedly by a busy loop in the engine.
23
+
24
+ Essentially, the scheduler produces a dictionary of {req_id: num_tokens}
25
+ that specifies how many tokens to process for each request in this
26
+ scheduling step. For example, num_tokens can be as large as the number
27
+ of prompt tokens for new requests, or it can be 1 for the requests that
28
+ are auto-regressively generating new tokens one by one. Otherwise, it
29
+ can be somewhere in between in case of chunked prefills, prefix caching,
30
+ speculative decoding, etc.
31
+
32
+ Additionally, the scheduler also returns useful data about each request
33
+ or the batch as a whole. The model runner will use this information in
34
+ preparing inputs to the model.
35
+
36
+ Returns:
37
+ A SchedulerOutput object containing information about the scheduled
38
+ requests.
39
+ """
40
+ raise NotImplementedError
41
+
42
+ @abstractmethod
43
+ def update_from_output(
44
+ self,
45
+ scheduler_output: "SchedulerOutput",
46
+ model_runner_output: "ModelRunnerOutput",
47
+ ) -> "EngineCoreOutputs":
48
+ """Update the scheduler state based on the model runner output.
49
+
50
+ This method is called after the model runner has processed the scheduled
51
+ requests. The model runner output includes generated token ids, draft
52
+ token ids for next step, etc. The scheduler uses this information to
53
+ update its states, checks the finished requests, and returns the output
54
+ for each request.
55
+
56
+ Returns:
57
+ A EngineCoreOutputs object containing the outputs for each request.
58
+ """
59
+ raise NotImplementedError
60
+
61
+ @abstractmethod
62
+ def add_request(self, request: "Request") -> None:
63
+ """Add a new request to the scheduler's internal queue.
64
+
65
+ Args:
66
+ request: The new request being added.
67
+ """
68
+ raise NotImplementedError
69
+
70
+ @abstractmethod
71
+ def finish_requests(
72
+ self,
73
+ request_ids: Union[str, Iterable[str]],
74
+ finished_status: "RequestStatus",
75
+ ) -> None:
76
+ """Finish the requests in the scheduler's internal queue. If the request
77
+ is not in the queue, this method will do nothing.
78
+
79
+ This method is called in two cases:
80
+ 1. When the request is aborted by the client.
81
+ 2. When the frontend process detects a stop string of the request after
82
+ de-tokenizing its generated tokens.
83
+
84
+ Args:
85
+ request_ids: A single or a list of request IDs.
86
+ finished_status: The finished status of the given requests.
87
+ """
88
+ raise NotImplementedError
89
+
90
+ @abstractmethod
91
+ def get_num_unfinished_requests(self) -> int:
92
+ """Number of unfinished requests in the scheduler's internal queue."""
93
+ raise NotImplementedError
94
+
95
+ def has_unfinished_requests(self) -> bool:
96
+ """Returns True if there are unfinished requests in the scheduler's
97
+ internal queue."""
98
+ return self.get_num_unfinished_requests() > 0
99
+
100
+ @abstractmethod
101
+ def has_finished_requests(self) -> bool:
102
+ """Returns True if there are finished requests that need to be cleared.
103
+ NOTE: This is different from `not self.has_unfinished_requests()`.
104
+
105
+ The scheduler maintains an internal list of the requests finished in the
106
+ previous step. This list is returned from the next call to schedule(),
107
+ to be sent to the model runner in the next step to clear cached states
108
+ for these finished requests.
109
+
110
+ This method checks if this internal list of finished requests is
111
+ non-empty. This information is useful for DP attention.
112
+ """
113
+ raise NotImplementedError
114
+
115
+ def has_requests(self) -> bool:
116
+ """Returns True if there are unfinished requests, or finished requests
117
+ not yet returned in SchedulerOutputs."""
118
+ return self.has_unfinished_requests() or self.has_finished_requests()
119
+
120
+ @abstractmethod
121
+ def get_num_unscheduled_requests(self) -> int:
122
+ """Number of requests that are not being processed by the executor."""
123
+ raise NotImplementedError
124
+
125
+ @abstractmethod
126
+ def reset_prefix_cache(self) -> bool:
127
+ """Reset the prefix cache for KV cache.
128
+
129
+ This is particularly required when the model weights are live-updated.
130
+ """
131
+ raise NotImplementedError
132
+
133
+ @abstractmethod
134
+ def make_stats(self) -> Optional["SchedulerStats"]:
135
+ """Make a SchedulerStats object for logging.
136
+
137
+ The SchedulerStats object is created for every scheduling step.
138
+ """
139
+ raise NotImplementedError
vllm/v1/core/sched/output.py
renamed
vllm/v1/core/sched/scheduler.py
renamed
+6
-31
@@ -13,8 +13,10 @@ from vllm.logger import init_logger
13
from vllm.v1.core.encoder_cache_manager import (EncoderCacheManager,
14
compute_encoder_budget)
15
from vllm.v1.core.kv_cache_manager import KVCacheManager
16
-from vllm.v1.core.scheduler_output import (CachedRequestData, NewRequestData,
17
- SchedulerOutput)
16
+from vllm.v1.core.sched.interface import SchedulerInterface
17
+from vllm.v1.core.sched.output import (CachedRequestData, NewRequestData,
18
+ SchedulerOutput)
19
+from vllm.v1.core.sched.utils import check_stop
20
from vllm.v1.engine import (EngineCoreEventType, EngineCoreOutput,
21
EngineCoreOutputs)
22
from vllm.v1.metrics.stats import SchedulerStats
@@ -25,7 +27,7 @@ from vllm.v1.structured_output import StructuredOutputManager
27
logger = init_logger(__name__)
28
29
28
-class Scheduler:
30
+class Scheduler(SchedulerInterface):
31
32
def __init__(
33
self,
@@ -602,7 +604,7 @@ class Scheduler:
604
605
# Check for stop and update request state.
606
# This must be called before we make the EngineCoreOutput.
605
- stopped = self._check_stop(request)
607
+ stopped = check_stop(request, self.max_model_len)
608
if stopped:
609
self._free_request(request)
610
break
@@ -648,25 +650,6 @@ class Scheduler:
650
scheduler_stats=self.make_stats(),
651
)
652
651
- def _check_stop(self, request: Request) -> bool:
652
- if (request.num_tokens >= self.max_model_len
653
- or request.num_output_tokens >= request.max_tokens):
654
- request.status = RequestStatus.FINISHED_LENGTH_CAPPED
655
- return True
656
-
657
- sampling_params = request.sampling_params
658
- last_token_id = request.output_token_ids[-1]
659
- if (not sampling_params.ignore_eos
660
- and last_token_id == request.eos_token_id):
661
- request.status = RequestStatus.FINISHED_STOPPED
662
- return True
663
-
664
- if last_token_id in (sampling_params.stop_token_ids or ()):
665
- request.status = RequestStatus.FINISHED_STOPPED
666
- request.stop_reason = last_token_id
667
- return True
668
- return False
669
-
653
def add_request(self, request: Request) -> None:
654
self.waiting.append(request)
655
self.requests[request.request_id] = request
@@ -715,17 +698,9 @@ class Scheduler:
698
def get_num_unfinished_requests(self) -> int:
699
return len(self.waiting) + len(self.running)
700
718
- def has_unfinished_requests(self) -> bool:
719
- return self.get_num_unfinished_requests() > 0
720
-
701
def has_finished_requests(self) -> bool:
702
return len(self.finished_req_ids) > 0
703
724
- def has_requests(self):
725
- """Returns True if there are unfinished requests, or finished requests
726
- not yet returned in SchedulerOutputs."""
727
- return self.has_unfinished_requests() or self.has_finished_requests()
728
-
704
def get_num_unscheduled_requests(self) -> int:
705
"""Number of requests that are not being processed by the executor."""
706
return self.get_num_unfinished_requests() - len(self.scheduled_req_ids)
vllm/v1/core/sched/utils.py
new
+22
@@ -0,0 +1,22 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+from vllm.v1.request import Request, RequestStatus
3
+
4
+
5
+def check_stop(request: Request, max_model_len: int) -> bool:
6
+ if (request.num_tokens >= max_model_len
7
+ or request.num_output_tokens >= request.max_tokens):
8
+ request.status = RequestStatus.FINISHED_LENGTH_CAPPED
9
+ return True
10
+
11
+ sampling_params = request.sampling_params
12
+ last_token_id = request.output_token_ids[-1]
13
+ if (not sampling_params.ignore_eos
14
+ and last_token_id == request.eos_token_id):
15
+ request.status = RequestStatus.FINISHED_STOPPED
16
+ return True
17
+
18
+ if last_token_id in (sampling_params.stop_token_ids or ()):
19
+ request.status = RequestStatus.FINISHED_STOPPED
20
+ request.stop_reason = last_token_id
21
+ return True
22
+ return False
vllm/v1/engine/core.py
+2
-2
@@ -22,8 +22,8 @@ from vllm.transformers_utils.config import (
22
from vllm.utils import (get_exception_traceback, resolve_obj_by_qualname,
23
zmq_socket_ctx)
24
from vllm.v1.core.kv_cache_utils import get_kv_cache_configs
25
-from vllm.v1.core.scheduler import Scheduler as V1Scheduler
26
-from vllm.v1.core.scheduler import SchedulerOutput
25
+from vllm.v1.core.sched.output import SchedulerOutput
26
+from vllm.v1.core.sched.scheduler import Scheduler as V1Scheduler
27
from vllm.v1.engine import (EngineCoreOutputs, EngineCoreRequest,
28
EngineCoreRequestType, UtilityOutput)
29
from vllm.v1.engine.mm_input_cache import MMInputCacheServer
vllm/v1/worker/gpu_model_runner.py
+1
-1
@@ -45,7 +45,7 @@ from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
45
if TYPE_CHECKING:
46
import xgrammar as xgr
47
48
- from vllm.v1.core.scheduler_output import SchedulerOutput
48
+ from vllm.v1.core.sched.output import SchedulerOutput
49
else:
50
xgr = LazyLoader("xgr", globals(), "xgrammar")
51
vllm/v1/worker/gpu_worker.py
+1
-1
@@ -28,7 +28,7 @@ from vllm.v1.worker.worker_base import WorkerBase
28
logger = init_logger(__name__)
29
30
if TYPE_CHECKING:
31
- from vllm.v1.core.scheduler_output import SchedulerOutput
31
+ from vllm.v1.core.sched.output import SchedulerOutput
32
33
34
class Worker(WorkerBase):
vllm/v1/worker/tpu_model_runner.py
+1
-1
@@ -37,7 +37,7 @@ from vllm.v1.utils import bind_kv_cache
37
from vllm.v1.worker.gpu_input_batch import CachedRequestState, InputBatch
38
39
if TYPE_CHECKING:
40
- from vllm.v1.core.scheduler import SchedulerOutput
40
+ from vllm.v1.core.sched.output import SchedulerOutput
41
42
logger = init_logger(__name__)
43
vllm/v1/worker/tpu_worker.py
+1
-1
@@ -17,7 +17,7 @@ from vllm.distributed import (ensure_model_parallel_initialized,
17
from vllm.logger import init_logger
18
from vllm.model_executor import set_random_seed
19
from vllm.utils import STR_DTYPE_TO_TORCH_DTYPE
20
-from vllm.v1.core.scheduler import SchedulerOutput
20
+from vllm.v1.core.sched.output import SchedulerOutput
21
from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
22
KVCacheSpec)
23
from vllm.v1.outputs import ModelRunnerOutput