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, 20:15 UTC
[Prefix Cache] Include lora_name in BlockStored event for deterministic KV-cache reconstruction (#27577)
Signed-off-by: Sage Ahrac <sagiahrak@gmail.com> Co-authored-by: Sage <80211083+sagiahrac@users.noreply.github.com>
Sage committed
Dec 30, 2025 at 02:17 UTC
39512aba72537d364e0352141cf8c0523115ec09
8 files changed
+49
examples/online_serving/kv_events_subscriber.py
+6
@@ -28,8 +28,14 @@ class BlockStored(KVCacheEvent):
28
parent_block_hash: ExternalBlockHash | None
29
token_ids: list[int]
30
block_size: int
31
+
32
lora_id: int | None
33
+ """Deprecated: use `lora_name` for KV block key hash.
34
+ Retained for backward compatibility.
35
+ """
36
+
37
medium: str | None
38
+ lora_name: str | None
39
40
41
class BlockRemoved(KVCacheEvent):
tests/v1/engine/test_engine_core_client.py
+1
@@ -733,6 +733,7 @@ def test_kv_cache_events(
733
)
734
assert event.parent_block_hash is None, "Parent block hash should be None"
735
assert event.lora_id is None, "Lora id should be None"
736
+ assert event.lora_name is None, "Lora name should be None"
737
assert len(event.token_ids) == num_blocks * block_size, (
738
"Token ids should be the same as the custom tokens"
739
)
tests/v1/kv_connector/unit/test_lmcache_connector.py
+30
@@ -25,6 +25,7 @@ def mock_lmcache_engine_event():
25
lora_id,
26
block_size,
27
medium,
28
+ lora_name,
29
):
30
self.block_hashes = block_hashes
31
self.parent_block_hash = parent_block_hash
@@ -32,6 +33,7 @@ def mock_lmcache_engine_event():
33
self.lora_id = lora_id
34
self.block_size = block_size
35
self.medium = medium
36
+ self.lora_name = lora_name
37
38
return MockEvent(
39
block_hashes=["hash1", "hash2"],
@@ -40,6 +42,7 @@ def mock_lmcache_engine_event():
42
lora_id=None,
43
block_size=16,
44
medium="GPU",
45
+ lora_name=None,
46
)
47
48
@@ -109,6 +112,7 @@ class TestGetKVConnectorKVCacheEvents:
112
assert events[0].lora_id is None
113
assert events[0].block_size == 16
114
assert events[0].medium == "GPU"
115
+ assert events[0].lora_name is None
116
117
def test_converts_multiple_events(self, mock_connector):
118
"""Test conversion of multiple events from lmcache engine format."""
@@ -121,6 +125,7 @@ class TestGetKVConnectorKVCacheEvents:
125
self.lora_id = None
126
self.block_size = 16
127
self.medium = "GPU"
128
+ self.lora_name = None
129
130
events = [MockEvent(i) for i in range(5)]
131
mock_connector._lmcache_engine.get_kv_events.return_value = events
@@ -150,6 +155,7 @@ class TestGetKVConnectorKVCacheEvents:
155
self.lora_id = 42
156
self.block_size = 32
157
self.medium = "DISK"
158
+ self.lora_name = "lora_example"
159
160
mock_connector._lmcache_engine.get_kv_events.return_value = [
161
MockEventWithLora()
@@ -166,6 +172,7 @@ class TestGetKVConnectorKVCacheEvents:
172
assert event.lora_id == 42
173
assert event.block_size == 32
174
assert event.medium == "DISK"
175
+ assert event.lora_name == "lora_example"
176
177
def test_handles_none_parent_block_hash(self, mock_connector):
178
"""Test handling of events with None parent_block_hash."""
@@ -178,6 +185,7 @@ class TestGetKVConnectorKVCacheEvents:
185
self.lora_id = None
186
self.block_size = 16
187
self.medium = "GPU"
188
+ self.lora_name = None
189
190
mock_connector._lmcache_engine.get_kv_events.return_value = [
191
MockEventNoParent()
@@ -223,6 +231,7 @@ class TestUpdateConnectorOutput:
231
block_size=16,
232
lora_id=None,
233
medium="GPU",
234
+ lora_name=None,
235
)
236
kv_events.add_events([event])
237
@@ -243,6 +252,7 @@ class TestUpdateConnectorOutput:
252
block_size=16,
253
lora_id=None,
254
medium="GPU",
255
+ lora_name=None,
256
)
257
existing_events.add_events([event1])
258
existing_events.add_events([event1]) # Simulate 2 workers reporting
@@ -258,6 +268,7 @@ class TestUpdateConnectorOutput:
268
block_size=16,
269
lora_id=None,
270
medium="GPU",
271
+ lora_name=None,
272
)
273
new_events.add_events([event2])
274
@@ -288,6 +299,7 @@ class TestUpdateConnectorOutput:
299
block_size=16,
300
lora_id=None,
301
medium="GPU",
302
+ lora_name=None,
303
)
304
new_events.add_events([event])
305
@@ -309,6 +321,7 @@ class TestUpdateConnectorOutput:
321
block_size=16,
322
lora_id=None,
323
medium="GPU",
324
+ lora_name=None,
325
)
326
events1.add_events([event1])
327
output1 = KVConnectorOutput(kv_cache_events=events1)
@@ -323,6 +336,7 @@ class TestUpdateConnectorOutput:
336
block_size=16,
337
lora_id=None,
338
medium="GPU",
339
+ lora_name=None,
340
)
341
events2.add_events([event2])
342
output2 = KVConnectorOutput(kv_cache_events=events2)
@@ -337,6 +351,7 @@ class TestUpdateConnectorOutput:
351
block_size=16,
352
lora_id=None,
353
medium="GPU",
354
+ lora_name=None,
355
)
356
events3.add_events([event3])
357
output3 = KVConnectorOutput(kv_cache_events=events3)
@@ -358,6 +373,7 @@ class TestUpdateConnectorOutput:
373
block_size=16,
374
lora_id=None,
375
medium="GPU",
376
+ lora_name=None,
377
)
378
events1.add_events([event1])
379
output1 = KVConnectorOutput(kv_cache_events=events1)
@@ -397,6 +413,7 @@ class TestTakeEvents:
413
block_size=16,
414
lora_id=None,
415
medium="GPU",
416
+ lora_name=None,
417
)
418
event2 = BlockStored(
419
block_hashes=["hash2"],
@@ -405,6 +422,7 @@ class TestTakeEvents:
422
block_size=16,
423
lora_id=None,
424
medium="GPU",
425
+ lora_name=None,
426
)
427
kv_events.add_events([event1, event2])
428
mock_connector._kv_cache_events = kv_events
@@ -431,6 +449,7 @@ class TestTakeEvents:
449
block_size=16,
450
lora_id=None,
451
medium="GPU",
452
+ lora_name=None,
453
)
454
uncommon_event = BlockStored(
455
block_hashes=["hash_uncommon"],
@@ -439,6 +458,7 @@ class TestTakeEvents:
458
block_size=16,
459
lora_id=None,
460
medium="GPU",
461
+ lora_name=None,
462
)
463
464
# All 3 workers report common_event
@@ -469,6 +489,7 @@ class TestTakeEvents:
489
block_size=16,
490
lora_id=None,
491
medium="GPU",
492
+ lora_name=None,
493
)
494
kv_events1.add_events([event1])
495
mock_connector._kv_cache_events = kv_events1
@@ -491,6 +512,7 @@ class TestTakeEvents:
512
block_size=16,
513
lora_id=None,
514
medium="GPU",
515
+ lora_name=None,
516
)
517
kv_events2.add_events([event2])
518
mock_connector._kv_cache_events = kv_events2
@@ -510,6 +532,7 @@ class TestTakeEvents:
532
block_size=16,
533
lora_id=None,
534
medium="GPU",
535
+ lora_name=None,
536
)
537
event2 = BlockStored(
538
block_hashes=["hash2"],
@@ -518,6 +541,7 @@ class TestTakeEvents:
541
block_size=16,
542
lora_id=None,
543
medium="GPU",
544
+ lora_name=None,
545
)
546
547
# Worker 1 reports event1
@@ -572,6 +596,7 @@ class TestIntegrationScenarios:
596
self.lora_id = None
597
self.block_size = 16
598
self.medium = "GPU"
599
+ self.lora_name = None
600
601
# Worker 1
602
mock_connector._lmcache_engine.get_kv_events.return_value = [
@@ -628,6 +653,7 @@ class TestIntegrationScenarios:
653
self.lora_id = None
654
self.block_size = 16
655
self.medium = "GPU"
656
+ self.lora_name = None
657
658
for cycle in range(3):
659
# Get events
@@ -667,6 +693,7 @@ class TestIntegrationScenarios:
693
block_size=16,
694
lora_id=None,
695
medium="GPU",
696
+ lora_name=None,
697
)
698
699
worker1_unique_event = BlockStored(
@@ -676,6 +703,7 @@ class TestIntegrationScenarios:
703
block_size=16,
704
lora_id=None,
705
medium="GPU",
706
+ lora_name=None,
707
)
708
709
worker2_unique_event = BlockStored(
@@ -685,6 +713,7 @@ class TestIntegrationScenarios:
713
block_size=16,
714
lora_id=None,
715
medium="GPU",
716
+ lora_name=None,
717
)
718
719
worker3_unique_event = BlockStored(
@@ -694,6 +723,7 @@ class TestIntegrationScenarios:
723
block_size=16,
724
lora_id=None,
725
medium="GPU",
726
+ lora_name=None,
727
)
728
729
# Create events for each worker
tests/v1/kv_connector/unit/test_offloading_connector.py
+1
@@ -528,6 +528,7 @@ def test_offloading_connector(request_runner):
528
assert event.token_ids == []
529
assert event.parent_block_hash is None
530
assert event.lora_id is None
531
+ assert event.lora_name is None
532
event = events[1]
533
assert isinstance(event, BlockRemoved)
534
assert event.block_hashes == to_hashes([4, 5, 6])
vllm/distributed/kv_events.py
+6
@@ -51,8 +51,14 @@ class BlockStored(KVCacheEvent):
51
parent_block_hash: ExternalBlockHash | None
52
token_ids: list[int]
53
block_size: int
54
+
55
lora_id: int | None
56
+ """Deprecated: use `lora_name` for KV block key hash.
57
+ Retained for backward compatibility.
58
+ """
59
+
60
medium: str | None
61
+ lora_name: str | None
62
63
def __hash__(self) -> int:
64
return hash(
vllm/distributed/kv_transfer/kv_connector/v1/lmcache_connector.py
+1
@@ -234,6 +234,7 @@ class LMCacheConnectorV1(KVConnectorBase_V1):
234
lora_id=e.lora_id,
235
block_size=e.block_size,
236
medium=e.medium,
237
+ lora_name=e.lora_name,
238
)
239
for e in events
240
]
vllm/distributed/kv_transfer/kv_connector/v1/offloading_connector.py
+1
@@ -406,6 +406,7 @@ class OffloadingConnectorScheduler:
406
lora_id=None,
407
block_size=event.block_size,
408
medium=event.medium,
409
+ lora_name=None,
410
)
411
412
vllm/v1/core/block_pool.py
+3
@@ -290,6 +290,9 @@ class BlockPool:
290
if request.lora_request
291
else None,
292
medium=MEDIUM_GPU,
293
+ lora_name=request.lora_request.name
294
+ if request.lora_request
295
+ else None,
296
)
297
)
298