From cf46defdddfc75578c926c56afda249ae3040842 Mon Sep 17 00:00:00 2001 From: leavelet Date: Fri, 12 Jun 2026 08:07:36 +0000 Subject: [PATCH] Harden the affinity wiring per opus review (F-1, F-2) - A chunked-led batch now seeds affinity_batch_warm_led from the CHUNK's class instead of the first loop candidate: a 65536 first chunk is cold-led (later colds are free density, caps bound them); latching from a warm follower inverted cold-led-admits-everything and could spuriously STOP the scan, under-filling the forward. - The policy additionally requires no LoRA and no L3 hicache-storage: their pre-classification continues make "first classified candidate" drift from the true FCFS head, mis-routing the defer accounting. Co-Authored-By: Claude Fable 5 --- python/sglang/srt/managers/scheduler.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index d970e08bb..0d6b4bb9d 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -2572,12 +2572,29 @@ class Scheduler( affinity_on = ( envs.SGLANG_CP_PREFILL_AFFINITY_GROUP.get() and not self.enable_priority_scheduling + # The pre-classification continues (LoRA compatibility, L3 + # prefetch pending) would make "first classified candidate" drift + # from the true FCFS head and mis-route the defer accounting — + # the policy is only exact (and only validated) without them. + and not self.enable_lora + and not self.enable_hicache_storage and self.server_args.enable_cp_shared_kv_prefill_bs_gt1 and is_nsa_prefill_cp_in_seq_split() ) affinity_window_used = 0 affinity_head_pending = True # first classified candidate = FCFS head affinity_batch_warm_led: Optional[bool] = None + if affinity_on and adder._chunked_req_in_batch is not None: + # A chunked-led batch must carry the CHUNK's class: a 65536 first + # chunk is cold-led (further colds are free density and the caps + # bound them); a tail chunk over a big cached prefix is warm-led. + # Latching from the first loop candidate instead would invert + # cold-led-admits-everything and spuriously STOP the scan. + chunked = adder._chunked_req_in_batch + affinity_batch_warm_led = ( + len(chunked.prefix_indices) + + int(getattr(chunked, "host_hit_length", 0) or 0) + ) >= int(envs.SGLANG_CP_PREFILL_AFFINITY_WARM_FLOOR.get()) for req in self.waiting_queue: if self.enable_lora and req.lora_id not in running_loras: @@ -2642,6 +2659,9 @@ class Scheduler( - (1 if adder._chunked_req_in_batch is not None else 0) == 0 ), + # None = nothing admitted yet; only reachable together + # with batch_empty_for_affinity=True (which decides + # first), so False is never read as a real class here. batch_warm_led=bool(affinity_batch_warm_led), is_head=is_head, head_defer_count=req.affinity_defer_count,