Support token low usage watermark in prefill delayer (#16814)

This commit is contained in:
fzyzcjy
2026-01-09 23:07:18 +08:00
committed by GitHub
parent 71e9c31cb8
commit 8eeffbe9aa
5 changed files with 241 additions and 22 deletions

View File

@@ -224,6 +224,7 @@ class Envs:
SGLANG_SCHEDULER_SKIP_ALL_GATHER = EnvBool(False)
SGLANG_SCHEDULER_DECREASE_PREFILL_IDLE = EnvBool(False)
SGLANG_PREFILL_DELAYER_MAX_DELAY_PASSES = EnvInt(30)
SGLANG_PREFILL_DELAYER_TOKEN_USAGE_LOW_WATERMARK = EnvFloat(None)
SGLANG_DATA_PARALLEL_BUDGET_INTERVAL = EnvInt(1)
# Test: pd-disaggregation

View File

@@ -31,6 +31,7 @@ class _NegotiateOutput(NamedTuple):
output_allow: bool
output_reason: str
num_prefillable: int
num_token_watermark_force_allow: int
class PrefillDelayer:
@@ -41,15 +42,19 @@ class PrefillDelayer:
cpu_group,
server_args,
max_delay_passes: int,
token_usage_low_watermark: Optional[float],
metrics_collector: Optional["SchedulerMetricsCollector"] = None,
):
self._max_delay_passes = max_delay_passes
self._token_usage_low_watermark = token_usage_low_watermark
logger.info(
f"PrefillDelayer initialized with max_delay_passes={self._max_delay_passes}"
f"PrefillDelayer initialized with "
f"max_delay_passes={self._max_delay_passes} "
f"token_usage_low_watermark={self._token_usage_low_watermark}"
)
self._global_info_buffer = torch.empty(
(dp_size, attn_tp_size, 1),
(dp_size, attn_tp_size, 2),
dtype=torch.int64,
device="cpu",
)
@@ -70,33 +75,53 @@ class PrefillDelayer:
), "To use PrefillDelayer, disable_overlap_schedule must be False."
def _negotiate_should_allow_prefill(
self, local_prefillable: bool
self, local_prefillable: bool, token_usage: float
) -> _NegotiateOutput:
out = self._negotiate_should_allow_prefill_pure(
prev_state=self._curr_state,
local_prefillable=local_prefillable,
token_usage=token_usage,
)
self._curr_state = out.next_state
return out
# (Almost) pure function, do not modify self state
def _negotiate_should_allow_prefill_pure(
self,
prev_state: Optional[_State],
local_prefillable: bool,
token_usage: float,
) -> _NegotiateOutput:
global_prefillable = self._gather_info(local_prefillable=local_prefillable)
# Compute local states
local_token_watermark_force_allow = (
local_prefillable
and ((x := self._token_usage_low_watermark) is not None)
and (token_usage < x)
)
# Gather global states
global_prefillable, global_token_watermark_force_allow = self._gather_info(
local_prefillable=local_prefillable,
local_token_watermark_force_allow=local_token_watermark_force_allow,
)
# Compute derived global states
if global_prefillable.min().item() > 0:
prefillable_status = "all"
elif global_prefillable.max().item() == 0:
prefillable_status = "none"
else:
prefillable_status = "mixed"
global_exists_token_watermark_force_allow = (
global_token_watermark_force_allow.max().item() > 0
)
debug_info = dict(
input_estimation=prefillable_status,
num_prefillable=global_prefillable.sum().item(),
num_token_watermark_force_allow=global_token_watermark_force_allow.sum().item(),
)
# Compute outputs
if prefillable_status == "all":
exist_previous_wait = prev_state is not None
return _NegotiateOutput(
@@ -108,11 +133,20 @@ class PrefillDelayer:
elif prefillable_status == "none":
return _NegotiateOutput(
next_state=None,
# It does not matter whether we allow or not, thus we allow for simplicity
output_allow=True,
output_reason="",
**debug_info,
)
elif prefillable_status == "mixed":
if global_exists_token_watermark_force_allow:
return _NegotiateOutput(
next_state=None,
output_allow=True,
output_reason="token_watermark",
**debug_info,
)
prev_delayed_count = prev_state.delayed_count if prev_state else 0
if prev_delayed_count < self._max_delay_passes - 1:
next_state = prev_state or _State()
@@ -133,9 +167,11 @@ class PrefillDelayer:
else:
raise NotImplementedError
def _gather_info(self, local_prefillable: bool):
def _gather_info(
self, local_prefillable: bool, local_token_watermark_force_allow: bool
):
local_info = torch.tensor(
[int(local_prefillable)],
[int(local_prefillable), int(local_token_watermark_force_allow)],
device="cpu",
dtype=torch.int64,
)
@@ -145,12 +181,13 @@ class PrefillDelayer:
group=self._cpu_group,
)
tp0_info = self._global_info_buffer[:, 0, :]
return tp0_info[:, 0]
return tp0_info[:, 0], tp0_info[:, 1]
class PrefillDelayerSinglePassExecutor:
def __init__(self, prefill_delayer: PrefillDelayer):
def __init__(self, prefill_delayer: PrefillDelayer, token_usage: float):
self._prefill_delayer = prefill_delayer
self._token_usage = token_usage
self._result: Optional[_NegotiateOutput] = None
@property
@@ -171,6 +208,7 @@ class PrefillDelayerSinglePassExecutor:
if not self._called:
self._result = self._prefill_delayer._negotiate_should_allow_prefill(
local_prefillable=local_prefillable,
token_usage=self._token_usage,
)
return self._result.output_allow
@@ -187,6 +225,13 @@ def _record_single_pass_result(
f"(num_prefillable={output.num_prefillable}, "
f"actual_execution={actual_execution})"
)
elif output.output_allow and (output.output_reason == "token_watermark"):
logger.info(
f"PrefillDelayer force allow prefill due to low watermark. "
f"(num_prefillable={output.num_prefillable}, "
f"num_token_watermark_force_allow={output.num_token_watermark_force_allow}, "
f"actual_execution={actual_execution})"
)
else:
assert output.output_reason in {
"",

View File

@@ -775,6 +775,9 @@ class Scheduler(
self.metrics_collector if self.enable_metrics else None
),
max_delay_passes=envs.SGLANG_PREFILL_DELAYER_MAX_DELAY_PASSES.get(),
token_usage_low_watermark=(
envs.SGLANG_PREFILL_DELAYER_TOKEN_USAGE_LOW_WATERMARK.get()
),
)
# Enable preemption for priority scheduling.
self.try_preemption = self.enable_priority_scheduling
@@ -1846,8 +1849,9 @@ class Scheduler(
def get_new_batch_prefill(self) -> Optional[ScheduleBatch]:
prefill_delayer_single_pass = None
if self.prefill_delayer:
_, token_usage, _, _ = self._get_token_info()
prefill_delayer_single_pass = PrefillDelayerSinglePassExecutor(
self.prefill_delayer
self.prefill_delayer, token_usage=token_usage
)
ret = self._get_new_batch_prefill_raw(

View File

@@ -826,9 +826,9 @@ class SchedulerMetricsCollector:
self.prefill_delayer_outcomes_total.labels(
**self.labels,
input_estimation=input_estimation,
output_allow=str(int(output_allow)),
output_allow=str(output_allow).lower(),
output_reason=output_reason,
actual_execution=str(int(actual_execution)),
actual_execution=str(actual_execution).lower(),
).inc(1)
def increment_retracted_reqs(