Repository navigation
Conversation
Signed-off-by: LyxWxj <yuxuanlou621@foxmail.com>
|
This bugfix can be much smaller. Successful resumptions currently skip the existing admission-bypass accounting, and blocked resumptions skip its limit check. Making both paths honor the existing fairness mechanism is enough to resolve the reproduced workloads; we do not need the new KV-capacity preflight APIs and retry-state machinery for this fix. Proposed change in @@ -515,25 +515,25 @@ class Scheduler:
self._enter_prefill(request)
else:
resume_result = self._try_resume(request)
- if resume_result == ResumeResult.RESTORED:
- active_count += 1
- continue
if resume_result == ResumeResult.BLOCKED:
deferred.append(request_id)
+ if request.admission_bypasses >= self.config.max_admission_bypasses:
+ break
continue
- plan = self._plan_admission(request, active_count)
- admitted = self._commit_admission(request, plan)
- while not admitted and self.preempt(request, waiting_only=True):
- # Recompute prefix claims and growth reservations after
- # releasing a resident allocation; the previous plan is stale.
+ if resume_result == ResumeResult.NOT_PREEMPTED:
plan = self._plan_admission(request, active_count)
admitted = self._commit_admission(request, plan)
- if not admitted:
- deferred.append(request_id)
- if request.admission_bypasses >= self.config.max_admission_bypasses:
- break
- continue
+ while not admitted and self.preempt(request, waiting_only=True):
+ # Recompute prefix claims and growth reservations after
+ # releasing a resident allocation; the previous plan is stale.
+ plan = self._plan_admission(request, active_count)
+ admitted = self._commit_admission(request, plan)
+ if not admitted:
+ deferred.append(request_id)
+ if request.admission_bypasses >= self.config.max_admission_bypasses:
+ break
+ continue
active_count += 1
for blocked_id in deferred:Validated on main |
…velock Signed-off-by: LyxWxj <yuxuanlou621@foxmail.com>
Successful restorations now fall through to the common admission accounting, so they increment the bypass counters of earlier blocked requests. Blocked restorations check max_admission_bypasses before the scan continues, letting active requests drain instead of livelocking under wavefront chunked prefill. Drop the KV-capacity preflight and failed-preemption retry state from the earlier workaround; both paths now use the existing fairness mechanism. Add regression coverage for the restored and blocked paths. Signed-off-by: LyxWxj <yuxuanlou621@foxmail.com>
|
@bjf-frz Thanks — agreed, and I've reduced the change to the patch you proposed. The root cause was the admission fairness accounting, not missing capacity I applied your Validation on head
I agree that eliminating the remaining ineffective preemptions is a separate Could you take another look at |
Required human contributor answers
The human contributor must answer all four questions before PR submission.
1. What outcome are you trying to achieve?
2. What is the highest-leverage variable for that outcome?
3. How will this work move that variable rather than create busywork?
4. Who is the customer or user, and what do they actually need?
PR necessity and bugfix evidence
Observed failure, reproduced locally against
upstream/maina87f6c8:tests/test_prefill_livelock.py::test_priority_staged_prefill_drains_under_cache_pressure[refill-True-seed-8]finishes only 3/10 requests in 500 engine steps with 460 preemptions. With the
fix it finishes 10/10 in 124 steps with 11 preemptions.
Purpose
Fix a scheduler livelock in priority admission under wavefront chunked prefill
and staged prefill: restore-heavy workloads could keep bypassing a blocked
request without ever letting active requests drain.
Scheduler._admit()already bounds bypasses withadmission_bypasses: eachsuccessful fresh admission increments the counters of earlier deferred
requests, and the scan stops at
max_admission_bypasses. The restoration pathsdid not participate:
ResumeResult.RESTOREDreturned before the common accounting, so successfulrestorations never incremented deferred bypass counters.
ResumeResult.BLOCKEDdeferred the request and continued without checkingits own counter against the limit.
As a result, a restore-heavy workload could bypass a blocked request
indefinitely and keep preempting/restoring without making progress. The fix
makes both paths honor the existing mechanism:
Revision note
This revision replaces the earlier KV-capacity preflight and retry-state
approach with the smaller fairness fix requested in review. The preflight and
retry state are removed:
Scheduler._priority_preemption_can_admitScheduler._available_admission_budgetScheduler._record_failed_priority_preemptionScheduler._blocked_priority_preemptionsScheduler.resume_blocks_callbackPreemptionManager.resume_required_blocksKVCacheManager.prefix_admission_boundsKVCacheManager.reclaimable_blocksThe corresponding tests and documentation were removed or reverted as well.
Those mechanisms may be a useful separate optimization, but they are not
required for this bugfix and duplicated the admission logic.
Scope and remaining limitation
The fix bounds repeated bypasses and restores forward progress. It does not
eliminate every ineffective preemption; reducing those can be a separate
optimization with its own benchmarks.
Test Plan
vllm-rlt Version / Commit: 0.2.0 /
cd67fc8onfix/wcpb-prefill-livelock,with a net diff against
upstream/maina87f6c8.Test Result
before the fix; 10/10 finished in 124 steps with 11 preemptions after it.
tests/test_prefill_livelock.py: 9 passed. Covers two arrival seeds acrossrefill/mixed modes with wavefront on/off, asynchronous scheduling, resumption
bypass accounting, and the blocked-restoration limit.
Workload outputs match the no-preemption reference; no KV allocations or
snapshots remain after each run.
maina87f6c8: 178 CPU regression/reproductiontests passed and 96/96 bounded workload cases passed for the same minimal
patch.
ruff checkandruff format --checkpass on the changed files.