Skip to content

refactor: eliminate DisaggPDChunkedPrefillScheduler.#2017

Open
weizhehuang0827 wants to merge 1 commit into
xLLM-AI:mainfrom
weizhehuang0827:feat/eliminate-pd-chunked-scheduler
Open

refactor: eliminate DisaggPDChunkedPrefillScheduler.#2017
weizhehuang0827 wants to merge 1 commit into
xLLM-AI:mainfrom
weizhehuang0827:feat/eliminate-pd-chunked-scheduler

Conversation

@weizhehuang0827

@weizhehuang0827 weizhehuang0827 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Description

Background

After the scheduler policy refactor (#1992), DisaggPDChunkedPrefillScheduler became the last scheduler subclass with its own prepare_batch() that bypasses the SchedulerPolicy abstraction. Its core responsibility was:

  1. Prevent hold-and-wait deadlock: In PD disagg mode, completed prefills must transfer KV to decode before releasing blocks. If multiple large requests each grab a partial chunk, none can complete → deadlock. The class enforced pd_prefill_footprint_fits to gate fresh request admission.
  2. Prioritize in-flight over fresh: Using a "deferred re-queue" pattern to skip blocked fresh requests while still giving in-flight chunked requests their tokens.

Both concerns are now handled by the general PrefillFirstPolicy:

  • Deadlock prevention: PrefillFirstPolicy schedules chunk_queue (in-flight) before prefill_queue (fresh). In-flight requests always get their chunks first → complete → release blocks → fresh can start. A new full-footprint admission gate in schedule_prefill_from_queue provides additional protection.
  • Priority ordering: Separate chunk_queue / prefill_queue naturally eliminates the need for deferred re-queue — a blocked fresh request in prefill_queue never starves an in-flight request in chunk_queue.

Full-Footprint Admission Gate

Added inside schedule_prefill_from_queue, gating fresh requests only (kv_cache_tokens_num == 0):

if (batch_mode_.enable_chunked_prefill &&
    request->sequences()[0]->kv_state().kv_cache_tokens_num() == 0) {
  if (reserved_full_footprint + full_footprint > total_blocks) {
    blocks_exhausted = true;
    break;
  }
}
// After successful admission:
reserved_full_footprint += full_footprint;
  • reserved_full_footprint is passed by reference from the policy layer, accumulating across both chunk_queue and prefill_queue scheduling passes.
  • In DecodeFirstPolicy, it's initialized with max_used_blocks * 1.1 to reserve margin for decode token generation (reduces preemption frequency).
  • In PrefillFirstPolicy, it starts at 0 (no decode on PD PREFILL instance).
  • In-flight chunked requests (kv_cache_tokens_num > 0) skip this check — they are already admitted and must be allowed to continue.

Other Adaptations

  • drain_request_queue: Skip expand_sequences for PD PREFILL instances (best_of_n expansion deferred to DECODE instance, preserving the optimization from the deleted class).
  • resolve_batch_mode: PD PREFILL instances always use PrefillFirstPolicy (exclusive batch), regardless of enable_mix_batch setting.
  • handle_unschedulable_head: Removed chunk_queue.empty() condition — if a stuck request in chunk_queue can never complete (footprint > total), it should also be error-reported.
  • record_num_prefix_cache_tokens(): Already present in schedule_prefill_from_queue (line 322), preserving the prefix cache propagation fix from bugfix: propagate prefix cache usage across pd. #2011.

What's Deleted

Deleted Replacement
DisaggPDChunkedPrefillScheduler class DisaggPDSchedulerContinuousScheduler::prepare_batch()PrefillFirstPolicy
DisaggPDScheduler::prepare_batch() override Inherited from ContinuousScheduler (drain handled by drain_request_queue)
pd_prefill_footprint_fits() Full-footprint gate in schedule_prefill_from_queue
pd_prefill_remaining_blocks() Not needed (scheduling order prevents deadlock)
pick_pd_chunk_budget() compute_prefill_tokens()
alloc_chunk() / match_prefix_blocks() allocate_for_prefill() / allocate_shared_blocks_for()
schedule_waiting_prefill() (deferred pattern) schedule_prefill_from_queue (chunk_queue first, then prefill_queue)
DISAGG_PD_CHUNKED_PREFILL enum Removed from SchedulerKind

Related Issues

Follow-up to #1992 (scheduler policy refactor)

Change Type

  • Bug fix
  • New feature
  • Performance improvement
  • Refactor
  • Documentation
  • Test
  • Build or CI

Pull Request Checklist

PR Title and Commit Messages

  • The PR title and each commit message follow the xLLM commit format.

Pre-commit Checks

  • pre-commit run --all-files passed.

Self Review

  • Self-reviewed per custom-code-style.md.
  • Rebased onto latest main.

Build and Test Coverage

  • Tests added (FullFootprintAdmissionGate, FullFootprintAdmitsBothWhenFits).
  • NPU: unit tests (42 PASSED) + PD serving stress tests passed.

Reviewer Notes

Test Results

Unit tests: 42 PASSED (scheduler_test)

PD Stress Tests (Qwen3-8B, single card, etcd + xllm_service):

Case Dataset Requests Concurrency Result
PD chunked=true ShareGPT 100 20 PASSED
PD chunked=true Random 2000in/100out 50 15 PASSED
PD chunked=false ShareGPT 100 20 PASSED
PD chunked=false Random 2000in/100out 50 15 PASSED

PD + chunked_prefill now goes through DisaggPDScheduler →
ContinuousScheduler::prepare_batch() → PrefillFirstPolicy, which
schedules chunk_queue (in-flight) before prefill_queue (fresh).
This naturally prevents the hold-and-wait deadlock that the deleted
class's pd_prefill_footprint_fits gate was designed to solve.

- Delete DisaggPDChunkedPrefillScheduler class and all helper functions
- Delete DisaggPDScheduler::prepare_batch() override (base handles it)
- Update drain_request_queue to skip expand_sequences for PD PREFILL
  (best_of_n expansion deferred to DECODE instance)
- Remove DISAGG_PD_CHUNKED_PREFILL from SchedulerKind enum
- Update scheduler_factory to route PD+chunked to DisaggPDScheduler
@weizhehuang0827 weizhehuang0827 changed the title refactor(scheduler): eliminate DisaggPDChunkedPrefillScheduler. refactor: eliminate DisaggPDChunkedPrefillScheduler. Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants