diff --git a/python/sglang/srt/managers/scheduler.py b/python/sglang/srt/managers/scheduler.py index 1a18102fd..1e74d2f8e 100644 --- a/python/sglang/srt/managers/scheduler.py +++ b/python/sglang/srt/managers/scheduler.py @@ -2654,6 +2654,11 @@ class Scheduler( return ClearHiCacheReqOutput(success=if_success) def is_fully_idle(self, for_health_check=False) -> bool: + # Health check piggybacks on running requests in process_output. + # Only running_batch + waiting_queue guarantee active GPU processing; + # disagg queues (bootstrap/prealloc/transfer) may have items without + # any request actually running on GPU — e.g. stuck handshake, full + # KV cache, or stalled transfer — so they can't carry health info. # Batch running status idle = ( self.running_batch.is_empty() @@ -2667,13 +2672,6 @@ class Scheduler( # Waiting queues: waiting + bootstrapping + preallocation + kv transfer (decode) idle &= len(self.waiting_queue) == 0 - if self.disaggregation_mode == DisaggregationMode.PREFILL: - idle &= len(self.disagg_prefill_bootstrap_queue.queue) == 0 - if self.disaggregation_mode == DisaggregationMode.DECODE: - idle &= ( - len(self.disagg_decode_prealloc_queue.queue) == 0 - and len(self.disagg_decode_transfer_queue.queue) == 0 - ) if not for_health_check: # Grammar queue and prefill inflight queue may not produce batch results @@ -2681,6 +2679,11 @@ class Scheduler( idle &= len(self.grammar_manager.grammar_queue) == 0 if self.disaggregation_mode == DisaggregationMode.PREFILL: idle &= len(self.disagg_prefill_inflight_queue) == 0 + idle &= len(self.disagg_prefill_bootstrap_queue.queue) == 0 + + if self.disaggregation_mode == DisaggregationMode.DECODE: + idle &= len(self.disagg_decode_prealloc_queue.queue) == 0 + idle &= len(self.disagg_decode_transfer_queue.queue) == 0 return idle