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 5, 12:15 UTC
[KV Offload] Clean up ARC/LRU refactoring leftovers: group ARC tests and fix stale comment (#38217)
Signed-off-by: Ronen Schaffer <ronen.schaffer@ibm.com>
Ronen Schaffer committed
Apr 7, 2026 at 15:14 UTC
7c139ab23f6d2e9b4603b40814956100a1ccf569
2 files changed
+297
-334
tests/v1/kv_offload/test_cpu_manager.py
+295
-331
@@ -15,6 +15,7 @@ from vllm.v1.kv_offload.abstract import (
15
from vllm.v1.kv_offload.cpu.manager import CPUOffloadingManager
16
from vllm.v1.kv_offload.cpu.policies.arc import ARCCachePolicy
17
from vllm.v1.kv_offload.mediums import CPULoadStoreSpec
18
+from vllm.v1.kv_offload.reuse_manager import FilterReusedOffloadingManager
19
20
21
@dataclass
@@ -243,335 +244,300 @@ def test_cpu_manager():
244
)
245
246
246
-def test_arc_manager_basic():
247
- """
248
- Tests CPUOffloadingManager with arc policy.
249
- Verifies that ARC handles store, load, and lookup operations correctly.
250
- """
251
- block_size = 256
252
- arc_manager = CPUOffloadingManager(
253
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=True
254
- )
255
- arc_policy = arc_manager._policy
256
- assert isinstance(arc_policy, ARCCachePolicy)
257
-
258
- # prepare store [1, 2]
259
- prepare_store_output = arc_manager.prepare_store(to_hashes([1, 2]))
260
- verify_store_output(
261
- prepare_store_output,
262
- ExpectedPrepareStoreOutput(
263
- block_hashes_to_store=[1, 2],
264
- store_block_ids=[0, 1],
265
- block_hashes_evicted=[],
266
- ),
267
- )
268
-
269
- # lookup [1, 2] -> not ready
270
- assert arc_manager.lookup(to_hashes([1, 2])) == 0
271
-
272
- # no events so far
273
- assert list(arc_manager.take_events()) == []
274
-
275
- # complete store [1, 2]
276
- arc_manager.complete_store(to_hashes([1, 2]))
277
- verify_events(
278
- arc_manager.take_events(), block_size=block_size, expected_stores=({1, 2},)
279
- )
280
-
281
- # lookup [1, 2]
282
- assert arc_manager.lookup(to_hashes([1])) == 1
283
- assert arc_manager.lookup(to_hashes([1, 2])) == 2
284
- assert arc_manager.lookup(to_hashes([1, 2, 3])) == 2
285
-
286
- # blocks should be in T1 (recent)
287
- assert len(arc_policy.t1) == 2
288
- assert len(arc_policy.t2) == 0
289
-
290
-
291
-def test_arc_manager_t1_to_t2_promotion():
292
- """
293
- Tests that accessing a block in T1 promotes it to T2 (frequent).
294
- This is a key feature of ARC's adaptive behavior.
295
- """
296
- block_size = 256
297
- arc_manager = CPUOffloadingManager(
298
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=False
299
- )
300
- arc_policy = arc_manager._policy
301
- assert isinstance(arc_policy, ARCCachePolicy)
302
-
303
- # store and complete block 1
304
- arc_manager.prepare_store(to_hashes([1]))
305
- arc_manager.complete_store(to_hashes([1]))
306
-
307
- # block 1 starts in T1 (recent)
308
- assert to_hashes([1])[0] in arc_policy.t1
309
- assert to_hashes([1])[0] not in arc_policy.t2
310
-
311
- # touch block 1 (simulate second access)
312
- arc_manager.touch(to_hashes([1]))
313
-
314
- # block 1 should now be in T2 (frequent)
315
- assert to_hashes([1])[0] not in arc_policy.t1
316
- assert to_hashes([1])[0] in arc_policy.t2
317
-
318
-
319
-def test_arc_manager_eviction_with_load():
320
- """
321
- Tests ARC eviction behavior similar to LRU test.
322
- Verifies that blocks being loaded (ref_cnt > 0) cannot be evicted.
323
- """
324
- block_size = 256
325
- arc_manager = CPUOffloadingManager(
326
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=True
327
- )
328
-
329
- # prepare and complete store [1, 2, 3, 4]
330
- prepare_store_output = arc_manager.prepare_store(to_hashes([1, 2, 3, 4]))
331
- verify_store_output(
332
- prepare_store_output,
333
- ExpectedPrepareStoreOutput(
334
- block_hashes_to_store=[1, 2, 3, 4],
335
- store_block_ids=[0, 1, 2, 3],
336
- block_hashes_evicted=[],
337
- ),
338
- )
339
- arc_manager.complete_store(to_hashes([1, 2, 3, 4]))
340
-
341
- # prepare load [2, 3] (increases ref_cnt)
342
- prepare_load_output = arc_manager.prepare_load(to_hashes([2, 3]))
343
- verify_load_output(prepare_load_output, [1, 2])
344
-
345
- # prepare store [5, 6, 7] with [2, 3] being loaded
346
- # should fail because [2, 3] have ref_cnt > 0
347
- assert arc_manager.prepare_store(to_hashes([5, 6, 7])) is None
348
-
349
- # complete load [2, 3]
350
- arc_manager.complete_load(to_hashes([2, 3]))
351
-
352
- # now prepare store [5, 6, 7] should succeed
353
- # ARC will evict blocks one at a time from T1 as needed
354
- prepare_store_output = arc_manager.prepare_store(to_hashes([5, 6, 7]))
355
- assert prepare_store_output is not None
356
- # Should successfully evict enough blocks to make room (at least 1)
357
- assert len(prepare_store_output.block_hashes_evicted) >= 1
358
-
359
-
360
-def test_arc_manager_adaptive_target():
361
- """
362
- Tests ARC's adaptive target adjustment via ghost lists.
363
- When a block in B1 (ghost list) is accessed, target_t1_size increases.
364
- When a block in B2 is accessed, target_t1_size decreases.
365
- """
366
- block_size = 256
367
- arc_manager = CPUOffloadingManager(
368
- block_size=block_size, num_blocks=2, cache_policy="arc", enable_events=False
369
- )
370
- arc_policy = arc_manager._policy
371
- assert isinstance(arc_policy, ARCCachePolicy)
372
-
373
- # store blocks 1, 2 (fills cache)
374
- arc_manager.prepare_store(to_hashes([1, 2]))
375
- arc_manager.complete_store(to_hashes([1, 2]))
376
-
377
- initial_target = arc_policy.target_t1_size
378
-
379
- # store block 3, evicting block 1 (moves to B1 ghost list)
380
- arc_manager.prepare_store(to_hashes([3]))
381
- arc_manager.complete_store(to_hashes([3]))
382
-
383
- # block 1 should be in B1 (ghost list)
384
- assert to_hashes([1])[0] in arc_policy.b1
385
-
386
- # touch block 1 (cache miss, but in B1)
387
- # this should increase target_t1_size (favor recency)
388
- arc_manager.touch(to_hashes([1]))
389
-
390
- # target should have increased
391
- assert arc_policy.target_t1_size > initial_target
392
-
393
-
394
-def test_arc_manager_t1_t2_eviction_policy():
395
- """
396
- Tests that ARC evicts from T1 or T2 based on target_t1_size.
397
- If |T1| >= target_t1_size, evict from T1, otherwise from T2.
398
- """
399
- block_size = 256
400
- arc_manager = CPUOffloadingManager(
401
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=False
402
- )
403
- arc_policy = arc_manager._policy
404
- assert isinstance(arc_policy, ARCCachePolicy)
405
-
406
- # store blocks 1, 2, 3, 4
407
- arc_manager.prepare_store(to_hashes([1, 2, 3, 4]))
408
- arc_manager.complete_store(to_hashes([1, 2, 3, 4]))
409
-
410
- # promote blocks 3, 4 to T2 by touching them
411
- arc_manager.touch(to_hashes([3, 4]))
412
-
413
- # now: T1 = {1, 2}, T2 = {3, 4}
414
- assert len(arc_policy.t1) == 2
415
- assert len(arc_policy.t2) == 2
416
-
417
- # set target_t1_size to prefer evicting from T1
418
- # (when |T1| >= target, evict from T1)
419
- arc_policy.target_t1_size = 1
420
-
421
- # store block 5, should evict from T1 (block 1, LRU in T1)
422
- output = arc_manager.prepare_store(to_hashes([5]))
423
- assert output is not None
424
- assert to_hashes([1]) == output.block_hashes_evicted
425
-
426
- arc_manager.complete_store(to_hashes([5]))
427
-
428
- # block 1 should be in B1 (ghost list)
429
- assert to_hashes([1])[0] in arc_policy.b1
430
- # block 5 should be in T1
431
- assert to_hashes([5])[0] in arc_policy.t1
432
-
433
-
434
-def test_arc_manager_ghost_list_bounds():
435
- """
436
- Tests that ghost lists (B1, B2) don't grow unbounded.
437
- They should be capped at cache_capacity.
438
- """
439
- block_size = 256
440
- arc_manager = CPUOffloadingManager(
441
- block_size=block_size, num_blocks=2, cache_policy="arc", enable_events=False
442
- )
443
- arc_policy = arc_manager._policy
444
- assert isinstance(arc_policy, ARCCachePolicy)
445
-
446
- # fill cache with blocks 1, 2
447
- arc_manager.prepare_store(to_hashes([1, 2]))
448
- arc_manager.complete_store(to_hashes([1, 2]))
449
-
450
- # store many blocks to fill ghost lists
451
- for i in range(3, 20):
452
- arc_manager.prepare_store(to_hashes([i]))
453
- arc_manager.complete_store(to_hashes([i]))
454
-
455
- # ghost lists should not exceed cache_capacity
456
- assert len(arc_policy.b1) <= arc_policy.cache_capacity
457
- assert len(arc_policy.b2) <= arc_policy.cache_capacity
458
-
459
-
460
-def test_arc_manager_touch_ordering():
461
- """
462
- Tests that touch() correctly updates access patterns.
463
- Similar to LRU test but verifies T1/T2 ordering.
464
- """
465
- block_size = 256
466
- arc_manager = CPUOffloadingManager(
467
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=True
468
- )
469
- arc_policy = arc_manager._policy
470
- assert isinstance(arc_policy, ARCCachePolicy)
471
-
472
- # store blocks 1, 2, 3, 4
473
- arc_manager.prepare_store(to_hashes([1, 2, 3, 4]))
474
- arc_manager.complete_store(to_hashes([1, 2, 3, 4]))
475
-
476
- # promote 3, 4 to T2
477
- arc_manager.touch(to_hashes([3, 4]))
478
-
479
- # T1 = {1, 2}, T2 = {3, 4}
480
- # touch [1, 3, 4] - should promote 1 to T2, and move 3,4 to end of T2
481
- arc_manager.touch(to_hashes([1, 3, 4]))
482
-
483
- # T1 = {2}, T2 = {1, 3, 4} (in that order, with 4 most recent)
484
- assert len(arc_policy.t1) == 1
485
- assert len(arc_policy.t2) == 3
486
-
487
- # store block 5, should evict from T1 (block 2, only one in T1)
488
- prepare_store_output = arc_manager.prepare_store(to_hashes([5]))
489
- verify_store_output(
490
- prepare_store_output,
491
- ExpectedPrepareStoreOutput(
492
- block_hashes_to_store=[5],
493
- store_block_ids=[1], # reuses block 2's storage
494
- block_hashes_evicted=[2],
495
- ),
496
- )
497
-
498
-
499
-def test_arc_manager_failed_store():
500
- """
501
- Tests that failed store operations clean up correctly.
502
- Similar to LRU test but for ARC.
503
- """
504
- block_size = 256
505
- arc_manager = CPUOffloadingManager(
506
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=True
507
- )
508
- arc_policy = arc_manager._policy
509
- assert isinstance(arc_policy, ARCCachePolicy)
510
-
511
- # store blocks 1, 2, 3, 4
512
- arc_manager.prepare_store(to_hashes([1, 2, 3, 4]))
513
- arc_manager.complete_store(to_hashes([1, 2, 3, 4]))
514
-
515
- # prepare store block 5 (will evict block 1)
516
- prepare_store_output = arc_manager.prepare_store(to_hashes([5]))
517
- assert prepare_store_output is not None
518
- assert len(prepare_store_output.block_hashes_evicted) == 1
519
-
520
- # complete store with failure
521
- arc_manager.complete_store(to_hashes([5]), success=False)
522
-
523
- # block 5 should not be in cache
524
- assert arc_manager.lookup(to_hashes([5])) == 0
525
- # block 5 should not be in T1 or T2
526
- assert to_hashes([5])[0] not in arc_policy.t1
527
- assert to_hashes([5])[0] not in arc_policy.t2
528
-
529
- # evicted block should still be gone (in B1 ghost list)
530
- evicted_hash = prepare_store_output.block_hashes_evicted[0]
531
- assert evicted_hash in arc_policy.b1
532
-
533
-
534
-def test_arc_manager_full_scenario():
535
- """
536
- Comprehensive test covering multiple ARC operations in sequence.
537
- Similar to the full LRU test but adapted for ARC behavior.
538
- """
539
- block_size = 256
540
- arc_manager = CPUOffloadingManager(
541
- block_size=block_size, num_blocks=4, cache_policy="arc", enable_events=True
542
- )
543
- arc_policy = arc_manager._policy
544
- assert isinstance(arc_policy, ARCCachePolicy)
545
-
546
- # store [1, 2]
547
- arc_manager.prepare_store(to_hashes([1, 2]))
548
- arc_manager.complete_store(to_hashes([1, 2]))
549
-
550
- # store [3, 4, 5] -> evicts [1]
551
- prepare_store_output = arc_manager.prepare_store(to_hashes([3, 4, 5]))
552
- assert prepare_store_output is not None
553
- assert len(prepare_store_output.block_hashes_evicted) == 1
554
- arc_manager.complete_store(to_hashes([3, 4, 5]))
555
-
556
- # promote some blocks to T2
557
- arc_manager.touch(to_hashes([2, 3]))
558
-
559
- # T1 has {4, 5}, T2 has {2, 3}
560
- assert len(arc_policy.t1) == 2
561
- assert len(arc_policy.t2) == 2
562
-
563
- # store [6] -> should evict from T1 (4 is oldest in T1)
564
- prepare_store_output = arc_manager.prepare_store(to_hashes([6]))
565
- assert prepare_store_output is not None
566
- arc_manager.complete_store(to_hashes([6]))
567
-
568
- # verify blocks 2, 3 (in T2) are still present
569
- assert arc_manager.lookup(to_hashes([2])) == 1
570
- assert arc_manager.lookup(to_hashes([3])) == 1
571
-
572
- # verify events
573
- events = list(arc_manager.take_events())
574
- assert len(events) > 0 # should have store and eviction events
247
+class TestARCPolicy:
248
+ """Unit tests for CPUOffloadingManager with ARC eviction policy."""
249
+
250
+ def _make_manager(
251
+ self, num_blocks: int = 4, enable_events: bool = True
252
+ ) -> tuple[CPUOffloadingManager, ARCCachePolicy]:
253
+ manager = CPUOffloadingManager(
254
+ block_size=256,
255
+ num_blocks=num_blocks,
256
+ cache_policy="arc",
257
+ enable_events=enable_events,
258
+ )
259
+ policy = manager._policy
260
+ assert isinstance(policy, ARCCachePolicy)
261
+ return manager, policy
262
+
263
+ def test_basic(self):
264
+ """
265
+ Tests CPUOffloadingManager with arc policy.
266
+ Verifies that ARC handles store, load, and lookup operations correctly.
267
+ """
268
+ cpu_manager, arc_policy = self._make_manager()
269
+
270
+ # prepare store [1, 2]
271
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([1, 2]))
272
+ verify_store_output(
273
+ prepare_store_output,
274
+ ExpectedPrepareStoreOutput(
275
+ block_hashes_to_store=[1, 2],
276
+ store_block_ids=[0, 1],
277
+ block_hashes_evicted=[],
278
+ ),
279
+ )
280
+
281
+ # lookup [1, 2] -> not ready
282
+ assert cpu_manager.lookup(to_hashes([1, 2])) == 0
283
+
284
+ # no events so far
285
+ assert list(cpu_manager.take_events()) == []
286
+
287
+ # complete store [1, 2]
288
+ cpu_manager.complete_store(to_hashes([1, 2]))
289
+ verify_events(
290
+ cpu_manager.take_events(), block_size=256, expected_stores=({1, 2},)
291
+ )
292
+
293
+ # lookup [1, 2]
294
+ assert cpu_manager.lookup(to_hashes([1])) == 1
295
+ assert cpu_manager.lookup(to_hashes([1, 2])) == 2
296
+ assert cpu_manager.lookup(to_hashes([1, 2, 3])) == 2
297
+
298
+ # blocks should be in T1 (recent)
299
+ assert len(arc_policy.t1) == 2
300
+ assert len(arc_policy.t2) == 0
301
+
302
+ def test_t1_to_t2_promotion(self):
303
+ """
304
+ Tests that accessing a block in T1 promotes it to T2 (frequent).
305
+ This is a key feature of ARC's adaptive behavior.
306
+ """
307
+ cpu_manager, arc_policy = self._make_manager(enable_events=False)
308
+
309
+ # store and complete block 1
310
+ cpu_manager.prepare_store(to_hashes([1]))
311
+ cpu_manager.complete_store(to_hashes([1]))
312
+
313
+ # block 1 starts in T1 (recent)
314
+ assert to_hashes([1])[0] in arc_policy.t1
315
+ assert to_hashes([1])[0] not in arc_policy.t2
316
+
317
+ # touch block 1 (simulate second access)
318
+ cpu_manager.touch(to_hashes([1]))
319
+
320
+ # block 1 should now be in T2 (frequent)
321
+ assert to_hashes([1])[0] not in arc_policy.t1
322
+ assert to_hashes([1])[0] in arc_policy.t2
323
+
324
+ def test_eviction_with_load(self):
325
+ """
326
+ Tests ARC eviction behavior similar to LRU test.
327
+ Verifies that blocks being loaded (ref_cnt > 0) cannot be evicted.
328
+ """
329
+ cpu_manager, _ = self._make_manager()
330
+
331
+ # prepare and complete store [1, 2, 3, 4]
332
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([1, 2, 3, 4]))
333
+ verify_store_output(
334
+ prepare_store_output,
335
+ ExpectedPrepareStoreOutput(
336
+ block_hashes_to_store=[1, 2, 3, 4],
337
+ store_block_ids=[0, 1, 2, 3],
338
+ block_hashes_evicted=[],
339
+ ),
340
+ )
341
+ cpu_manager.complete_store(to_hashes([1, 2, 3, 4]))
342
+
343
+ # prepare load [2, 3] (increases ref_cnt)
344
+ prepare_load_output = cpu_manager.prepare_load(to_hashes([2, 3]))
345
+ verify_load_output(prepare_load_output, [1, 2])
346
+
347
+ # prepare store [5, 6, 7] with [2, 3] being loaded
348
+ # should fail because [2, 3] have ref_cnt > 0
349
+ assert cpu_manager.prepare_store(to_hashes([5, 6, 7])) is None
350
+
351
+ # complete load [2, 3]
352
+ cpu_manager.complete_load(to_hashes([2, 3]))
353
+
354
+ # now prepare store [5, 6, 7] should succeed
355
+ # ARC will evict blocks one at a time from T1 as needed
356
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([5, 6, 7]))
357
+ assert prepare_store_output is not None
358
+ # Should successfully evict enough blocks to make room (at least 1)
359
+ assert len(prepare_store_output.block_hashes_evicted) >= 1
360
+
361
+ def test_adaptive_target(self):
362
+ """
363
+ Tests ARC's adaptive target adjustment via ghost lists.
364
+ When a block in B1 (ghost list) is accessed, target_t1_size increases.
365
+ When a block in B2 is accessed, target_t1_size decreases.
366
+ """
367
+ cpu_manager, arc_policy = self._make_manager(num_blocks=2, enable_events=False)
368
+
369
+ # store blocks 1, 2 (fills cache)
370
+ cpu_manager.prepare_store(to_hashes([1, 2]))
371
+ cpu_manager.complete_store(to_hashes([1, 2]))
372
+
373
+ initial_target = arc_policy.target_t1_size
374
+
375
+ # store block 3, evicting block 1 (moves to B1 ghost list)
376
+ cpu_manager.prepare_store(to_hashes([3]))
377
+ cpu_manager.complete_store(to_hashes([3]))
378
+
379
+ # block 1 should be in B1 (ghost list)
380
+ assert to_hashes([1])[0] in arc_policy.b1
381
+
382
+ # touch block 1 (cache miss, but in B1)
383
+ # this should increase target_t1_size (favor recency)
384
+ cpu_manager.touch(to_hashes([1]))
385
+
386
+ # target should have increased
387
+ assert arc_policy.target_t1_size > initial_target
388
+
389
+ def test_t1_t2_eviction_policy(self):
390
+ """
391
+ Tests that ARC evicts from T1 or T2 based on target_t1_size.
392
+ If |T1| >= target_t1_size, evict from T1, otherwise from T2.
393
+ """
394
+ cpu_manager, arc_policy = self._make_manager(enable_events=False)
395
+
396
+ # store blocks 1, 2, 3, 4
397
+ cpu_manager.prepare_store(to_hashes([1, 2, 3, 4]))
398
+ cpu_manager.complete_store(to_hashes([1, 2, 3, 4]))
399
+
400
+ # promote blocks 3, 4 to T2 by touching them
401
+ cpu_manager.touch(to_hashes([3, 4]))
402
+
403
+ # now: T1 = {1, 2}, T2 = {3, 4}
404
+ assert len(arc_policy.t1) == 2
405
+ assert len(arc_policy.t2) == 2
406
+
407
+ # set target_t1_size to prefer evicting from T1
408
+ # (when |T1| >= target, evict from T1)
409
+ arc_policy.target_t1_size = 1
410
+
411
+ # store block 5, should evict from T1 (block 1, LRU in T1)
412
+ output = cpu_manager.prepare_store(to_hashes([5]))
413
+ assert output is not None
414
+ assert to_hashes([1]) == output.block_hashes_evicted
415
+
416
+ cpu_manager.complete_store(to_hashes([5]))
417
+
418
+ # block 1 should be in B1 (ghost list)
419
+ assert to_hashes([1])[0] in arc_policy.b1
420
+ # block 5 should be in T1
421
+ assert to_hashes([5])[0] in arc_policy.t1
422
+
423
+ def test_ghost_list_bounds(self):
424
+ """
425
+ Tests that ghost lists (B1, B2) don't grow unbounded.
426
+ They should be capped at cache_capacity.
427
+ """
428
+ cpu_manager, arc_policy = self._make_manager(num_blocks=2, enable_events=False)
429
+
430
+ # fill cache with blocks 1, 2
431
+ cpu_manager.prepare_store(to_hashes([1, 2]))
432
+ cpu_manager.complete_store(to_hashes([1, 2]))
433
+
434
+ # store many blocks to fill ghost lists
435
+ for i in range(3, 20):
436
+ cpu_manager.prepare_store(to_hashes([i]))
437
+ cpu_manager.complete_store(to_hashes([i]))
438
+
439
+ # ghost lists should not exceed cache_capacity
440
+ assert len(arc_policy.b1) <= arc_policy.cache_capacity
441
+ assert len(arc_policy.b2) <= arc_policy.cache_capacity
442
+
443
+ def test_touch_ordering(self):
444
+ """
445
+ Tests that touch() correctly updates access patterns.
446
+ Similar to LRU test but verifies T1/T2 ordering.
447
+ """
448
+ cpu_manager, arc_policy = self._make_manager()
449
+
450
+ # store blocks 1, 2, 3, 4
451
+ cpu_manager.prepare_store(to_hashes([1, 2, 3, 4]))
452
+ cpu_manager.complete_store(to_hashes([1, 2, 3, 4]))
453
+
454
+ # promote 3, 4 to T2
455
+ cpu_manager.touch(to_hashes([3, 4]))
456
+
457
+ # T1 = {1, 2}, T2 = {3, 4}
458
+ # touch [1, 3, 4] - should promote 1 to T2, and move 3,4 to end of T2
459
+ cpu_manager.touch(to_hashes([1, 3, 4]))
460
+
461
+ # T1 = {2}, T2 = {1, 3, 4} (in that order, with 4 most recent)
462
+ assert len(arc_policy.t1) == 1
463
+ assert len(arc_policy.t2) == 3
464
+
465
+ # store block 5, should evict from T1 (block 2, only one in T1)
466
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([5]))
467
+ verify_store_output(
468
+ prepare_store_output,
469
+ ExpectedPrepareStoreOutput(
470
+ block_hashes_to_store=[5],
471
+ store_block_ids=[1], # reuses block 2's storage
472
+ block_hashes_evicted=[2],
473
+ ),
474
+ )
475
+
476
+ def test_failed_store(self):
477
+ """
478
+ Tests that failed store operations clean up correctly.
479
+ Similar to LRU test but for ARC.
480
+ """
481
+ cpu_manager, arc_policy = self._make_manager()
482
+
483
+ # store blocks 1, 2, 3, 4
484
+ cpu_manager.prepare_store(to_hashes([1, 2, 3, 4]))
485
+ cpu_manager.complete_store(to_hashes([1, 2, 3, 4]))
486
+
487
+ # prepare store block 5 (will evict block 1)
488
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([5]))
489
+ assert prepare_store_output is not None
490
+ assert len(prepare_store_output.block_hashes_evicted) == 1
491
+
492
+ # complete store with failure
493
+ cpu_manager.complete_store(to_hashes([5]), success=False)
494
+
495
+ # block 5 should not be in cache
496
+ assert cpu_manager.lookup(to_hashes([5])) == 0
497
+ # block 5 should not be in T1 or T2
498
+ assert to_hashes([5])[0] not in arc_policy.t1
499
+ assert to_hashes([5])[0] not in arc_policy.t2
500
+
501
+ # evicted block should still be gone (in B1 ghost list)
502
+ evicted_hash = prepare_store_output.block_hashes_evicted[0]
503
+ assert evicted_hash in arc_policy.b1
504
+
505
+ def test_full_scenario(self):
506
+ """
507
+ Comprehensive test covering multiple ARC operations in sequence.
508
+ Similar to the full LRU test but adapted for ARC behavior.
509
+ """
510
+ cpu_manager, arc_policy = self._make_manager()
511
+
512
+ # store [1, 2]
513
+ cpu_manager.prepare_store(to_hashes([1, 2]))
514
+ cpu_manager.complete_store(to_hashes([1, 2]))
515
+
516
+ # store [3, 4, 5] -> evicts [1]
517
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([3, 4, 5]))
518
+ assert prepare_store_output is not None
519
+ assert len(prepare_store_output.block_hashes_evicted) == 1
520
+ cpu_manager.complete_store(to_hashes([3, 4, 5]))
521
+
522
+ # promote some blocks to T2
523
+ cpu_manager.touch(to_hashes([2, 3]))
524
+
525
+ # T1 has {4, 5}, T2 has {2, 3}
526
+ assert len(arc_policy.t1) == 2
527
+ assert len(arc_policy.t2) == 2
528
+
529
+ # store [6] -> should evict from T1 (4 is oldest in T1)
530
+ prepare_store_output = cpu_manager.prepare_store(to_hashes([6]))
531
+ assert prepare_store_output is not None
532
+ cpu_manager.complete_store(to_hashes([6]))
533
+
534
+ # verify blocks 2, 3 (in T2) are still present
535
+ assert cpu_manager.lookup(to_hashes([2])) == 1
536
+ assert cpu_manager.lookup(to_hashes([3])) == 1
537
+
538
+ # verify events
539
+ events = list(cpu_manager.take_events())
540
+ assert len(events) > 0 # should have store and eviction events
541
542
543
def test_filter_reused_manager():
@@ -583,8 +549,6 @@ def test_filter_reused_manager():
549
block_size=block_size, num_blocks=4, cache_policy="lru", enable_events=True
550
)
551
586
- from vllm.v1.kv_offload.reuse_manager import FilterReusedOffloadingManager
587
-
552
manager = FilterReusedOffloadingManager(
553
backing=lru_manager, store_threshold=2, max_tracker_size=3
554
)
vllm/v1/kv_offload/reuse_manager.py
+2
-3
@@ -93,9 +93,8 @@ class FilterReusedOffloadingManager(OffloadingManager):
93
]
94
95
# Delegate to the backing manager with only the eligible hashes.
96
- # Passing an empty list is intentional and safe — both
97
- # LRUOffloadingManager and ARCOffloadingManager handle it correctly,
98
- # returning a PrepareStoreOutput with empty lists.
96
+ # Passing an empty list is intentional and safe — CPUOffloadingManager
97
+ # handles it correctly, returning a PrepareStoreOutput with empty lists.
98
return self._backing.prepare_store(eligible)
99
100
# ------------------------------------------------------------------