Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7d66ba9
feat: Add metrics based on priority-based scheduling.
Jan 13, 2026
8676199
[fix] Incorrect parameter naming
Jan 13, 2026
999d4df
fix lint
huangtingwei9988 Jan 15, 2026
e1ef8c3
Merge branch 'main' into priority_metrics_dev
huangtingwei9988 Jan 15, 2026
bfad739
Merge branch 'main' into priority_metrics_dev
huangtingwei9988 Jan 20, 2026
a582950
[fix] Logic issue in the num_running_reqs_by_priority tracking record.
Jan 22, 2026
f9b834a
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Jan 22, 2026
53785e5
Merge branch 'main' into priority_metrics_dev
huangtingwei9988 Jan 26, 2026
bfe854a
[fix] Optimize priority scheduling logic.
Feb 4, 2026
311721f
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 4, 2026
934758c
[fix] Optimize priority scheduling logic.
Feb 4, 2026
e5dc82b
[fix] Code formatting issues
Feb 8, 2026
38c8494
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 8, 2026
6922329
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 11, 2026
16eb3cc
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 12, 2026
a5f2c62
Merge branch 'main' into priority_metrics_dev
JustinTong0323 Feb 12, 2026
683b39b
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 21, 2026
347ade2
[fix] Optimize the logic of the log_prefill_stats interface
Feb 22, 2026
046b3b8
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 22, 2026
168a589
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 23, 2026
eb75cca
Merge branch 'main' into priority_metrics_dev
zhuxinjie-nz Feb 23, 2026
878ddaa
[PD-Disagg] Support query dp rank from bootstrap server. (#19168)
hnyls2002 Feb 23, 2026
5e1202e
[CI] fix the teardown output of disaggregation test (#19193)
hnyls2002 Feb 23, 2026
c9a5157
add new ci user (#19133)
narutolhy Feb 23, 2026
96516d5
[CI] Tiny enhance the dp attention load blance benchmark (#19194)
hnyls2002 Feb 23, 2026
2fada5b
[PD-Disagg] Unify prefill info data transition flow, all with `Prefil…
hnyls2002 Feb 24, 2026
3c5de3f
fix: patch docker image fixes (#19100)
dougyster Feb 24, 2026
55c7122
Whisper model support & `/v1/audio/transcriptions` endpoint & benchma…
JustinTong0323 Feb 24, 2026
95eb0c7
fix: add missing blank line after docstring in serving_transcription.…
Kangyan-Zhou Feb 24, 2026
1191c05
[PD-Disagg] Deduplicate common KVManager methods into CommonKVManager…
hnyls2002 Feb 24, 2026
c30d115
fix(docker): migrate ROCm Dockerfiles from setuptools-rust to maturin…
slin1237 Feb 24, 2026
11dc631
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 24, 2026
6078c7c
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 24, 2026
06813e7
[fix] Optimize the preempt logic
Feb 24, 2026
5d8dd95
[fix] Optimize the preemption logic
Feb 24, 2026
b5c0a8d
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 25, 2026
bf7cdf5
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Feb 25, 2026
cc90291
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Mar 1, 2026
1f8cebe
Merge remote-tracking branch 'origin/main' into priority_metrics_dev
Mar 4, 2026
6f3e93d
fix wrong server args naming
hnyls2002 Mar 4, 2026
81ccd78
rename confusing name
hnyls2002 Mar 4, 2026
8491531
fix wrong comments
hnyls2002 Mar 4, 2026
56e9b44
simplify the code
hnyls2002 Mar 4, 2026
0724cbe
use QueueCount
hnyls2002 Mar 4, 2026
cdbaf59
fix import error
hnyls2002 Mar 4, 2026
f9d66ad
add comments
hnyls2002 Mar 4, 2026
b14152c
fix dict copy & label type
hnyls2002 Mar 4, 2026
75251d9
Add unit test for priority scheduling metrics
hnyls2002 Mar 4, 2026
e7257b7
fix future
hnyls2002 Mar 4, 2026
6f957cb
fix
hnyls2002 Mar 4, 2026
4504bcf
tiny fix
hnyls2002 Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions python/sglang/srt/managers/scheduler.py

Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Contributor Author

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.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import signal
import sys
import time
from collections import deque
from collections import defaultdict, deque
from dataclasses import dataclass
from http import HTTPStatus
from typing import Any, Deque, Dict, List, Optional, Tuple, Union
Expand Down Expand Up @@ -786,7 +786,10 @@ def init_schedule_policy(self):
token_usage_low_watermark=self.server_args.prefill_delayer_token_usage_low_watermark,
)
# Enable preemption for priority scheduling.
self.try_preemption = self.enable_priority_scheduling
self.try_preemption = (
self.enable_priority_scheduling
and self.server_args.enable_try_preemption_by_priority
)
self.init_new_token_ratio = min(
envs.SGLANG_INIT_NEW_TOKEN_RATIO.get()
* self.server_args.schedule_conservativeness,
Expand Down Expand Up @@ -1928,6 +1931,10 @@ def _get_new_batch_prefill_raw(
return None

running_bs = len(self.running_batch.reqs)
num_running_reqs_by_priority = defaultdict(int)
if self.enable_priority_scheduling:
for req in self.running_batch.reqs:
num_running_reqs_by_priority[req.priority] += 1
# Ignore the check if self.chunked_req is not None.
# In the non-PP case, when self.chunked_req is not None, num_allocatable_reqs should always be greater than 0,
# as the space for the chunked requests has just been released.
Expand Down Expand Up @@ -2014,6 +2021,10 @@ def _get_new_batch_prefill_raw(
continue

running_bs = len(self.running_batch.reqs)
if self.enable_priority_scheduling:
num_running_reqs_by_priority.clear()
for running_req in self.running_batch.reqs:
num_running_reqs_by_priority[running_req.priority] += 1
if len(adder.can_run_list) >= self.get_num_allocatable_reqs(running_bs):
self.running_batch.batch_is_full = True
if self.disaggregation_mode == DisaggregationMode.PREFILL:
Expand Down Expand Up @@ -2100,6 +2111,7 @@ def _get_new_batch_prefill_raw(
can_run_list,
running_bs=len(self.running_batch.reqs),
running_bs_offline_batch=0,
num_running_reqs_by_priority=num_running_reqs_by_priority,
)

# Record metrics
Expand Down
88 changes: 87 additions & 1 deletion python/sglang/srt/managers/scheduler_metrics_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 log_decode_stats). This code duplication makes the code harder to maintain.

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 counts

Then 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

Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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]
(
Expand Down
18 changes: 18 additions & 0 deletions python/sglang/srt/managers/tokenizer_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ def init_model_config(self):
self.context_len = self.model_config.context_len
self.image_token_id = self.model_config.image_token_id
self.max_req_input_len = None # Will be set later in engine.py
self.enable_priority_scheduling = server_args.enable_priority_scheduling
self.default_priority_value = server_args.default_priority_value
speculative_algorithm = SpeculativeAlgorithm.from_string(
server_args.speculative_algorithm
)
Expand Down Expand Up @@ -432,6 +434,8 @@ def init_metric_collector_watchdog(self):
"model_name": self.server_args.served_model_name,
# TODO: Add lora name/path in the future,
}
if self.enable_priority_scheduling:
labels["priority"] = ""
if self.server_args.tokenizer_metrics_allowed_custom_labels:
for label in self.server_args.tokenizer_metrics_allowed_custom_labels:
labels[label] = ""
Expand Down Expand Up @@ -493,6 +497,7 @@ async def generate_request(

# Normalize the request
obj.normalize_batch_and_arguments()
self._set_default_priority(obj)
if self.enable_trace:
self._trace_request_start(obj, created_time, request)
if self.server_args.language_only:
Expand Down Expand Up @@ -1934,6 +1939,10 @@ def collect_metrics(self, state: ReqState, recv_obj: BatchStrOutput, i: int):
if custom_labels
else self.metrics_collector.labels
)
if self.enable_priority_scheduling:
priority = getattr(state.obj, "priority", None)
if priority is not None:
labels["priority"] = priority
if (
state.first_token_time == 0.0
and self.disaggregation_mode != DisaggregationMode.PREFILL
Expand Down Expand Up @@ -2283,6 +2292,15 @@ def _handle_epd_disaggregation_encode_request(
):
self.mm_receiver.send_encode_request(obj)

def _set_default_priority(self, obj: Union[GenerateReqInput, EmbeddingReqInput]):
"""Set the default priority value."""
if (
self.enable_priority_scheduling
and obj.priority is None
and self.default_priority_value is not None
):
obj.priority = self.default_priority_value


class ServerStatus(Enum):
Up = "Up"
Expand Down
Loading
Loading