From 32c8973ce8a17ad6a84f2baedacaa4f8f25b3607 Mon Sep 17 00:00:00 2001 From: Lianmin Zheng Date: Thu, 9 Jul 2026 17:55:26 -0700 Subject: [PATCH] Configurable decode retraction order (#30573) Co-authored-by: tanujtiwari1998 <168470992+tanujtiwari1998@users.noreply.github.com> --- python/sglang/srt/managers/schedule_batch.py | 69 +++++++++++++++----- python/sglang/srt/server_args.py | 18 +++++ 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/python/sglang/srt/managers/schedule_batch.py b/python/sglang/srt/managers/schedule_batch.py index 20a76eb81..873df3b14 100755 --- a/python/sglang/srt/managers/schedule_batch.py +++ b/python/sglang/srt/managers/schedule_batch.py @@ -40,6 +40,7 @@ import copy import dataclasses import logging import re +import sys from array import array from concurrent.futures import Future from enum import Enum, auto @@ -2552,21 +2553,13 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin): self, server_args: ServerArgs ) -> Tuple[List[Req], float, List[Req]]: """Retract the decoding requests when there is not enough memory.""" - sorted_indices = list(range(len(self.reqs))) - - # TODO(lsyin): improve retraction policy for radix cache - # For spec decoding, filter_batch API can only filter - # requests from the back, so we can only retract from the back. - # TODO(sang): Clean up finish path and support better retract - # policy. - if not server_args.speculative_algorithm: - sorted_indices.sort( - key=lambda i: ( - len(self.reqs[i].output_ids), - -len(self.reqs[i].origin_input_ids), - ), - reverse=True, - ) + sorted_indices = self._get_decode_retraction_order( + self.reqs, + server_args, + allow_policy_sort=( + self.spec_algorithm is None or self.spec_algorithm.is_none() + ), + ) retracted_reqs = [] first_iter = True @@ -2612,6 +2605,52 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin): return retracted_reqs, new_estimate_ratio, reqs_to_abort + @staticmethod + def _get_decode_retraction_order( + reqs: List[Req], server_args: ServerArgs, *, allow_policy_sort: bool + ) -> List[int]: + """Return indices ordered from most-preferred to least-preferred to keep. + + The retraction loop pops from the end of this list, so the least-preferred + request is retracted first. + """ + sorted_indices = list(range(len(reqs))) + + # TODO(lsyin): improve retraction policy for radix cache + # For spec decoding, filter_batch API can only filter requests from the + # back, so we can only retract from the back. + # TODO(sang): Clean up finish path and support better retract policy. + if not allow_policy_sort: + return sorted_indices + + def length_key(req: Req) -> Tuple[int, int]: + return (len(req.output_ids), -len(req.origin_input_ids)) + + if server_args.retraction_policy == "priority": + priority_sign = 1 if server_args.schedule_low_priority_values_first else -1 + + def retraction_key(req: Req) -> Tuple[int, int, int]: + priority = req.priority + if priority is None: + priority = ( + sys.maxsize + if server_args.schedule_low_priority_values_first + else -sys.maxsize - 1 + ) + return (priority * (-priority_sign), *length_key(req)) + + sorted_indices.sort( + key=lambda i: retraction_key(reqs[i]), + reverse=True, + ) + return sorted_indices + + sorted_indices.sort( + key=lambda i: length_key(reqs[i]), + reverse=True, + ) + return sorted_indices + def release_req(self, idx: int, remaing_req_count: int, server_args: ServerArgs): release_req( req=self.reqs[idx], diff --git a/python/sglang/srt/server_args.py b/python/sglang/srt/server_args.py index 5de5fb76c..3461b8aeb 100644 --- a/python/sglang/srt/server_args.py +++ b/python/sglang/srt/server_args.py @@ -292,6 +292,7 @@ FP4_GEMM_RUNNER_BACKEND_CHOICES = [ BF16_GEMM_BACKEND_CHOICES = ["auto", "cutedsl"] RADIX_EVICTION_POLICY_CHOICES = ["lru", "lfu", "slru", "priority"] +RETRACTION_POLICY_CHOICES = ["length", "priority"] RL_ON_POLICY_TARGET_CHOICES = ["fsdp"] @@ -736,6 +737,19 @@ class ServerArgs: int, "Minimum difference in priorities for an incoming request to have to preempt running request(s).", ] = 10 + retraction_policy: A[ + str, + Arg( + help=( + "The decode retraction policy to use when the KV cache is full. " + "'length' preserves the existing behavior and retracts short-output, " + "long-input requests first. 'priority' retracts lower-priority " + "requests first, using the same priority direction as priority " + "scheduling." + ), + choices=RETRACTION_POLICY_CHOICES, + ), + ] = "length" schedule_conservativeness: A[ float, "How conservative the schedule policy is. A larger value means more conservative scheduling. Use a larger value if you see requests being retracted frequently.", @@ -7004,6 +7018,10 @@ class ServerArgs: logger.warning( "--default-priority-value has no effect without --enable-priority-scheduling" ) + if self.retraction_policy == "priority" and not self.enable_priority_scheduling: + raise ValueError( + "--retraction-policy priority requires --enable-priority-scheduling" + ) # Check hisparse # Moved to the resolution pipeline (arg_groups/overrides.py: