A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 09:00 UTC
[BugFix] scheduler: Fix resuming of preempted requests after async load (#31583)
Signed-off-by: Or Ozeri <oro@il.ibm.com>
Or Ozeri committed
Jan 10, 2026 at 22:39 UTC
028599739d617b76c55179a9dabddb458e1fe035
3 files changed
+28
-4
tests/v1/core/test_scheduler.py
+21
-2
@@ -1261,10 +1261,11 @@ def test_kv_connector_unable_to_allocate(use_ec_connector, ec_role):
1261
assert len(scheduler.waiting) == 0
1262
1263
1264
+@pytest.mark.parametrize("is_async", [False, True])
1265
@pytest.mark.parametrize(
1266
"use_ec_connector, ec_role", [(False, None), (True, "ec_consumer")]
1267
)
1267
-def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1268
+def test_kv_connector_handles_preemption(is_async, use_ec_connector, ec_role):
1269
"""
1270
Test whether scheduler with KVConnector is able to handle
1271
unable to allocate (run out of blocks in allocate_slots().
@@ -1277,7 +1278,9 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1278
NUM_MATCHED_NEW_TOKENS = BLOCK_SIZE
1279
scheduler = create_scheduler(
1280
enable_prefix_caching=True,
1280
- use_kv_connector=mock_kv(matched_tokens=NUM_MATCHED_NEW_TOKENS, is_async=False),
1281
+ use_kv_connector=mock_kv(
1282
+ matched_tokens=NUM_MATCHED_NEW_TOKENS, is_async=is_async
1283
+ ),
1284
block_size=BLOCK_SIZE,
1285
num_blocks=NUM_BLOCKS,
1286
# encoder connector should not affect test results
@@ -1315,6 +1318,12 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1318
1319
# All can be scheduled - 1st token.
1320
output = scheduler.schedule()
1321
+ if is_async:
1322
+ assert len(scheduler.waiting) == 2
1323
+ assert scheduler.running == []
1324
+ _step_until_kv_transfer_finished(scheduler, req_ids)
1325
+ output = scheduler.schedule()
1326
+
1327
_assert_right_scheduler_output(
1328
output,
1329
# 2 remote kv cache hits.
@@ -1367,6 +1376,12 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1376
# Restarts the preempted request - generate 3rd token.
1377
# This will have a local and remote cache hit.
1378
output = scheduler.schedule()
1379
+ if is_async:
1380
+ waiting_req_ids = [req.request_id for req in scheduler.waiting]
1381
+ assert len(waiting_req_ids) == 1
1382
+ _step_until_kv_transfer_finished(scheduler, waiting_req_ids)
1383
+ output = scheduler.schedule()
1384
+
1385
_assert_right_scheduler_output(
1386
output,
1387
# 1 remote kv_cache hit!
@@ -1377,6 +1392,8 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1392
)
1393
assert len(scheduler.running) == 1
1394
assert len(scheduler.waiting) == 0
1395
+ assert output.scheduled_cached_reqs.num_reqs == 1
1396
+ assert output.scheduled_new_reqs == []
1397
_ = scheduler.update_from_output(output, MODEL_RUNNER_OUTPUT)
1398
assert len(scheduler.running) == 1
1399
assert len(scheduler.waiting) == 0
@@ -1389,6 +1406,8 @@ def test_kv_connector_handles_preemption(use_ec_connector, ec_role):
1406
num_requests=0,
1407
expected_num_scheduled_tokens=1,
1408
)
1409
+ assert output.scheduled_cached_reqs.num_reqs == 1
1410
+ assert output.scheduled_new_reqs == []
1411
assert len(scheduler.running) == 1
1412
_ = scheduler.update_from_output(output, MODEL_RUNNER_OUTPUT)
1413
assert len(scheduler.running) == 0
vllm/v1/core/sched/scheduler.py
+6
-1
@@ -445,7 +445,12 @@ class Scheduler(SchedulerInterface):
445
if request.status == RequestStatus.WAITING_FOR_REMOTE_KVS:
446
is_ready = self._update_waiting_for_remote_kv(request)
447
if is_ready:
448
- request.status = RequestStatus.WAITING
448
+ if request.num_preemptions:
449
+ # We must be loading for a resumed preemption
450
+ # rather than a new request.
451
+ request.status = RequestStatus.PREEMPTED
452
+ else:
453
+ request.status = RequestStatus.WAITING
454
else:
455
logger.debug(
456
"%s is still in WAITING_FOR_REMOTE_KVS state.",
vllm/v1/request.py
+1
-1
@@ -123,7 +123,7 @@ class Request:
123
# indicates that the output is corrupted
124
self.num_nans_in_logits = 0
125
126
- # The number of requests being preempted by the scheduler
126
+ # The number of times this request has been preempted by the scheduler.
127
self.num_preemptions = 0
128
129
# The number of tokens that have been computed remotely.