-
Notifications
You must be signed in to change notification settings - Fork 7.6k
feat: Priority-based scheduling optimization (including default priority, preemption toggle, priority-based metrics, etc.) #17026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
7d66ba9
8676199
999d4df
e1ef8c3
bfad739
a582950
f9b834a
53785e5
bfe854a
311721f
934758c
e5dc82b
38c8494
6922329
16eb3cc
a5f2c62
683b39b
347ade2
046b3b8
168a589
eb75cca
878ddaa
5e1202e
c9a5157
96516d5
2fada5b
3c5de3f
55c7122
95eb0c7
1191c05
c30d115
11dc631
6078c7c
06813e7
5d8dd95
b5c0a8d
bf7cdf5
cc90291
1f8cebe
6f3e93d
81ccd78
8491531
56e9b44
0724cbe
cdbaf59
f9d66ad
b14152c
75251d9
e7257b7
6f957cb
4504bcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,6 +107,8 @@ def init_metrics( | |
| "pp_rank": pp_rank, | ||
| "moe_ep_rank": self.moe_ep_rank, | ||
| } | ||
| if self.enable_priority_scheduling: | ||
| labels["priority"] = "" | ||
| if dp_rank is not None: | ||
| labels["dp_rank"] = dp_rank | ||
| self.metrics_collector = SchedulerMetricsCollector( | ||
|
|
@@ -150,6 +152,7 @@ def log_prefill_stats( | |
| can_run_list: List[Req], | ||
| running_bs: int, | ||
| running_bs_offline_batch: int, | ||
| num_running_reqs_by_priority: dict[int, int] = None, | ||
| ): | ||
| gap_latency = time.perf_counter() - self.last_prefill_stats_tic | ||
| self.last_prefill_stats_tic = time.perf_counter() | ||
|
|
@@ -229,6 +232,7 @@ def log_prefill_stats( | |
| ) | ||
|
|
||
| self.stats.num_running_reqs = running_bs | ||
| self.stats.num_running_reqs_by_priority = num_running_reqs_by_priority | ||
| self.stats.num_running_reqs_offline_batch = running_bs_offline_batch | ||
| self.stats.num_used_tokens = num_used | ||
| self.stats.token_usage = token_usage | ||
|
|
@@ -237,6 +241,11 @@ def log_prefill_stats( | |
| if self.is_hybrid_ssm: | ||
| self.stats.mamba_usage = mamba_usage | ||
| self.stats.num_queue_reqs = len(self.waiting_queue) | ||
| if self.enable_priority_scheduling: | ||
| num_queue_reqs_by_priority: dict[int, int] = defaultdict(int) | ||
| for req in self.waiting_queue: | ||
| num_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_queue_reqs_by_priority = num_queue_reqs_by_priority | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for counting requests by priority is repeated multiple times in this file for different queues (e.g., lines 257-265, 278-286, and also in To improve this, you could extract the counting logic into a helper method. For example: from collections import defaultdict
from typing import Iterable, Dict
def _compute_reqs_by_priority(self, req_queue: Iterable[Req]) -> Dict[int, int]:
"""Computes the number of requests for each priority in a queue."""
counts = defaultdict(int)
for req in req_queue:
counts[req.priority] += 1
return countsThen you can use it like this: if self.enable_priority_scheduling:
self.stats.num_queue_reqs_by_priority = self._compute_reqs_by_priority(self.waiting_queue)This would make the code cleaner and more maintainable. |
||
| self.stats.num_grammar_queue_reqs = len(self.grammar_manager) | ||
| self.stats.cache_hit_rate = cache_hit_rate | ||
|
|
||
|
|
@@ -255,6 +264,23 @@ def log_prefill_stats( | |
| self.stats.num_prefill_inflight_queue_reqs = len( | ||
| self.disagg_prefill_inflight_queue | ||
| ) | ||
| if self.enable_priority_scheduling: | ||
| num_prefill_prealloc_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_prefill_bootstrap_queue.queue: | ||
| num_prefill_prealloc_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_prefill_prealloc_queue_reqs_by_priority = ( | ||
| num_prefill_prealloc_queue_reqs_by_priority | ||
| ) | ||
| num_prefill_inflight_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_prefill_inflight_queue: | ||
| num_prefill_inflight_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_prefill_inflight_queue_reqs_by_priority = ( | ||
| num_prefill_inflight_queue_reqs_by_priority | ||
| ) | ||
| self.stats.kv_transfer_speed_gb_s = self.kv_transfer_speed_gb_s | ||
| self.stats.kv_transfer_latency_ms = self.kv_transfer_latency_ms | ||
| self.stats.kv_transfer_bootstrap_ms = self.kv_transfer_bootstrap_ms | ||
|
|
@@ -267,6 +293,23 @@ def log_prefill_stats( | |
| self.stats.num_decode_transfer_queue_reqs = len( | ||
| self.disagg_decode_transfer_queue.queue | ||
| ) | ||
| if self.enable_priority_scheduling: | ||
| num_decode_prealloc_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_decode_prealloc_queue.queue: | ||
| num_decode_prealloc_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_decode_prealloc_queue_reqs_by_priority = ( | ||
| num_decode_prealloc_queue_reqs_by_priority | ||
| ) | ||
| num_decode_transfer_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_decode_transfer_queue.queue: | ||
| num_decode_transfer_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_decode_transfer_queue_reqs_by_priority = ( | ||
| num_decode_transfer_queue_reqs_by_priority | ||
| ) | ||
|
|
||
| # Others | ||
| self.calculate_utilization() | ||
|
|
@@ -299,6 +342,10 @@ def log_decode_stats( | |
|
|
||
| self.num_generated_tokens = 0 | ||
| num_running_reqs = len(batch.reqs) | ||
| num_running_reqs_by_priority: dict[int, int] = defaultdict(int) | ||
| if self.enable_priority_scheduling: | ||
| for req in batch.reqs: | ||
| num_running_reqs_by_priority[req.priority] += 1 | ||
| num_running_reqs_offline_batch = 0 | ||
|
|
||
| # TODO: generalize this for various memory pools | ||
|
|
@@ -400,6 +447,7 @@ def log_decode_stats( | |
| if self.enable_metrics: | ||
| # Basics | ||
| self.stats.num_running_reqs = num_running_reqs | ||
| self.stats.num_running_reqs_by_priority = num_running_reqs_by_priority | ||
| self.stats.num_running_reqs_offline_batch = num_running_reqs_offline_batch | ||
| self.stats.num_used_tokens = num_used | ||
| self.stats.token_usage = token_usage | ||
|
|
@@ -410,6 +458,11 @@ def log_decode_stats( | |
| self.stats.decode_sum_seq_lens = batch.seq_lens_cpu.sum().item() | ||
| self.stats.gen_throughput = self.last_gen_throughput | ||
| self.stats.num_queue_reqs = len(self.waiting_queue) | ||
| if self.enable_priority_scheduling: | ||
| num_queue_reqs_by_priority: dict[int, int] = defaultdict(int) | ||
| for req in self.waiting_queue: | ||
| num_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_queue_reqs_by_priority = num_queue_reqs_by_priority | ||
| self.stats.num_grammar_queue_reqs = len(self.grammar_manager) | ||
| self.stats.cache_hit_rate = cache_hit_rate | ||
|
|
||
|
|
@@ -432,14 +485,47 @@ def log_decode_stats( | |
| self.stats.num_prefill_inflight_queue_reqs = len( | ||
| self.disagg_prefill_inflight_queue | ||
| ) | ||
| if self.enable_priority_scheduling: | ||
| num_prefill_prealloc_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_prefill_bootstrap_queue.queue: | ||
| num_prefill_prealloc_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_prefill_prealloc_queue_reqs_by_priority = ( | ||
| num_prefill_prealloc_queue_reqs_by_priority | ||
| ) | ||
| num_prefill_inflight_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_prefill_inflight_queue: | ||
| num_prefill_inflight_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_prefill_inflight_queue_reqs_by_priority = ( | ||
| num_prefill_inflight_queue_reqs_by_priority | ||
| ) | ||
| elif self.disaggregation_mode == DisaggregationMode.DECODE: | ||
| self.stats.num_decode_prealloc_queue_reqs = len( | ||
| self.disagg_decode_prealloc_queue.queue | ||
| ) | ||
| self.stats.num_decode_transfer_queue_reqs = len( | ||
| self.disagg_decode_transfer_queue.queue | ||
| ) | ||
|
|
||
| if self.enable_priority_scheduling: | ||
| num_decode_prealloc_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_decode_prealloc_queue.queue: | ||
| num_decode_prealloc_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_decode_prealloc_queue_reqs_by_priority = ( | ||
| num_decode_prealloc_queue_reqs_by_priority | ||
| ) | ||
| num_decode_transfer_queue_reqs_by_priority: dict[int, int] = ( | ||
| defaultdict(int) | ||
| ) | ||
| for req in self.disagg_decode_transfer_queue.queue: | ||
| num_decode_transfer_queue_reqs_by_priority[req.priority] += 1 | ||
| self.stats.num_decode_transfer_queue_reqs_by_priority = ( | ||
| num_decode_transfer_queue_reqs_by_priority | ||
| ) | ||
| running_routing_keys = [r.routing_key for r in batch.reqs] | ||
| waiting_routing_keys = [r.routing_key for r in self.waiting_queue] | ||
| ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to make one or two helper functions that calculate per-priority request counts in scheduler.py and scheduler_metrics_mixin.py and consolidate the usage?
Perhaps that a) takes in the list of requests and return xxx_reqs_by_priority dictionary or b) additionally taking the dictionary and updating in place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much — I’ll work on fixing these issues.