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 8, 15:15 UTC
[LMCache] Token Base IPC API (#34175)
Signed-off-by: Oasis-Git <ayw.sirius19@gmail.com>
Yuwei An committed
Feb 9, 2026 at 17:18 UTC
e94ec597334d9a3e9b0d04bc17152e2747c83d51
2 files changed
+376
-90
vllm/distributed/kv_transfer/kv_connector/v1/lmcache_integration/multi_process_adapter.py
+344
-73
@@ -20,16 +20,42 @@ from lmcache.v1.multiprocess.protocol import RequestType, get_response_class
20
logger = init_logger(__name__)
21
22
23
-def wrap_kv_caches(kv_caches: dict[str, KVCache]) -> KVCache:
23
+def wrap_kv_caches(kv_caches: dict[str, torch.Tensor]) -> KVCache:
24
logger.info("KV caches keys are %s", list(kv_caches.keys()))
25
return [CudaIPCWrapper(tensor) for tensor in kv_caches.values()]
26
27
28
+def striding_block_hashes(
29
+ block_hashes: list[bytes], blocks_in_chunk: int
30
+) -> Iterable[bytes]:
31
+ """Extract chunk-level hashes from block hashes by striding.
32
+
33
+ In hash-based vLLM, each vLLM block has its own hash. LMCache chunks
34
+ span ``blocks_in_chunk`` consecutive blocks. The representative hash
35
+ for a chunk is the hash of the **last** block in that chunk (because
36
+ each block hash already encodes its prefix). So we start at index
37
+ ``blocks_in_chunk - 1`` and stride by ``blocks_in_chunk``.
38
+ """
39
+ return islice(block_hashes, blocks_in_chunk - 1, None, blocks_in_chunk)
40
+
41
+
42
def send_lmcache_request(
43
mq_client: MessageQueueClient,
44
request_type: RequestType,
45
payloads: list[Any],
46
) -> MessagingFuture[Any]:
47
+ """
48
+ Helper function to send the request to the LMCache multiprocess server
49
+
50
+ Args:
51
+ mq_client: The LMCache multiprocess mode message queue client
52
+ request_type: The request type
53
+ payloads: The request payloads
54
+
55
+ Returns:
56
+ A messaging future for the request
57
+ """
58
+
59
future = mq_client.submit_request(
60
request_type, payloads, get_response_class(request_type)
61
)
@@ -39,40 +65,44 @@ def send_lmcache_request(
65
def get_lmcache_chunk_size(
66
mq_client: MessageQueueClient,
67
) -> int:
42
- future = send_lmcache_request(mq_client, RequestType.GET_CHUNK_SIZE, [])
43
- chunk_size = future.result()
44
- return chunk_size
68
+ """
69
+ Helper function to get the LMCache chunk size from the server
70
71
+ Args:
72
+ mq_client: The LMCache multiprocess mode message queue client
73
47
-def striding_block_hashes(
48
- block_hashes: list[bytes],
49
- blocks_in_chunk,
50
-) -> Iterable[bytes]:
51
- """Striding the block hashes to get the block hashes for each chunk.
52
- For example, if blocks_in_chunk is 16, then we will get the block hashes
53
- for the 16th, 32nd, 48th, ... blocks.
74
+ Returns:
75
+ An integer representing the LMCache chunk size
76
"""
55
- return islice(block_hashes, blocks_in_chunk - 1, None, blocks_in_chunk)
77
+ future = send_lmcache_request(mq_client, RequestType.GET_CHUNK_SIZE, [])
78
+ chunk_size = future.result()
79
+ return chunk_size
80
81
82
@dataclass
83
class LoadStoreOp:
60
- block_hashes: list[bytes]
84
block_ids: list[int]
85
+ """Block ids for the load/store operation"""
86
63
- def __len__(self) -> int:
64
- return len(self.block_hashes)
87
+ token_ids: list[int] | None = None
88
+ """Token IDs for the load/store operation (token mode)"""
89
66
- def __post_init__(self):
67
- assert len(self.block_hashes) == len(self.block_ids), (
68
- "The number of block hashes should be equal to the number of block ids "
69
- f"But got {len(self.block_hashes)} and {len(self.block_ids)}"
70
- )
90
+ block_hashes: list[bytes] | None = None
91
+ """Block hashes for the load/store operation (hash mode)"""
92
+
93
+ start: int = 0
94
+ """Start token index (token mode only)"""
95
+
96
+ end: int = 0
97
+ """End token index (token mode only)"""
98
+
99
+ def __len__(self) -> int:
100
+ return len(self.block_ids)
101
102
103
StoreResult = bool
104
RetrieveResult = list[bool]
75
-LookupResult = list[bool]
105
+LookupResult = int
106
107
108
class LMCacheMPSchedulerAdapter:
@@ -95,10 +125,6 @@ class LMCacheMPSchedulerAdapter:
125
kv_rank: The kv rank used for LMCache keys
126
vllm_block_size: The block size used in vLLM
127
"""
98
- logger.warning(
99
- "Importing LMCacheMPSchedulerAdapter is deprecated. "
100
- "Please update your LMCache to the latest version."
101
- )
128
self.mq_client = MessageQueueClient(server_url, context)
129
130
# Request futures
@@ -116,22 +142,89 @@ class LMCacheMPSchedulerAdapter:
142
self.blocks_in_chunk = self.chunk_size // vllm_block_size
143
144
@_lmcache_nvtx_annotate
119
- def maybe_submit_lookup_request(self, request_id: str, block_hashes: list[bytes]):
145
+ def maybe_submit_lookup_request(
146
+ self,
147
+ request_id: str,
148
+ block_hashes: list[bytes] | None = None,
149
+ token_ids: list[int] | None = None,
150
+ ) -> None:
151
+ """
152
+ Submit a new lookup request to LMCache if there is no ongoing request.
153
+
154
+ Supports both token-based and hash-based vLLM:
155
+ - token_ids: token IDs (token-based vLLM) -> single token-mode key
156
+ - block_hashes: block hashes (hash-based vLLM) -> strided hash-mode keys
157
+
158
+ Exactly one of block_hashes or token_ids must be provided.
159
+
160
+ Args:
161
+ request_id: The ID of the lookup request. The same ID indicates it's
162
+ from the same request
163
+ block_hashes: Block hashes to lookup from LMCache (hash mode)
164
+ token_ids: Token IDs to lookup from LMCache (token mode)
165
+
166
+ Returns:
167
+ None
168
+
169
+ Notes:
170
+ This function will have a side-effect: submitting a look up request to
171
+ LMCache, which will essentially 'lock' the KV cache chunks in the LMCache
172
+ for later retrieve operations.
173
+ In the meantime, this function will record the lookup request, and the
174
+ status of the look up request can be checked by `check_lookup_result`.
175
+ """
176
if request_id in self.lookup_futures:
177
# Skip if there is already a lookup request
178
return
179
124
- s = striding_block_hashes(block_hashes, self.blocks_in_chunk)
125
- keys = [self._create_key(block_hash) for block_hash in s]
180
+ assert (block_hashes is None) != (token_ids is None), (
181
+ "Exactly one of block_hashes or token_ids must be provided"
182
+ )
183
+
184
+ if block_hashes is not None:
185
+ # Hash mode: stride block hashes -> N hash-mode keys
186
+ chunk_hashes = list(
187
+ striding_block_hashes(block_hashes, self.blocks_in_chunk)
188
+ )
189
+ keys = [
190
+ self._create_hash_key(ch, request_id=request_id) for ch in chunk_hashes
191
+ ]
192
+ else:
193
+ # Token mode: truncate to chunk-aligned length
194
+ assert token_ids is not None
195
+ aligned_end = (len(token_ids) // self.chunk_size) * self.chunk_size
196
+ if aligned_end == 0:
197
+ return
198
+ keys = [
199
+ self._create_key(
200
+ token_ids,
201
+ start=0,
202
+ end=aligned_end,
203
+ request_id=request_id,
204
+ ).no_worker_id_version()
205
+ ]
206
+
207
future = send_lmcache_request(
208
self.mq_client,
209
RequestType.LOOKUP,
129
- [keys, True],
210
+ [keys],
211
)
212
self.lookup_futures[request_id] = future
213
214
@_lmcache_nvtx_annotate
215
def check_lookup_result(self, request_id: str) -> int | None:
216
+ """
217
+ Check the result of a previously submitted lookup request.
218
+
219
+ Args:
220
+ request_id: The ID of the lookup request submitted in
221
+ `maybe_submit_lookup_request`
222
+
223
+ Returns:
224
+ An integer representing the total number of tokens matched
225
+ in LMCache (prefix matching), or
226
+ None if the lookup request is not finished yet.
227
+ """
228
assert request_id in self.lookup_futures, (
229
f"Lookup request for request_id={request_id} has not been submitted"
230
)
@@ -141,7 +234,7 @@ class LMCacheMPSchedulerAdapter:
234
return None
235
236
result = future.result()
144
- num_chunks = sum(result)
237
+ num_chunks = result
238
return num_chunks * self.chunk_size
239
240
def num_blocks_per_chunk(self) -> int:
@@ -159,14 +252,47 @@ class LMCacheMPSchedulerAdapter:
252
"""
253
self.lookup_futures.pop(request_id, None)
254
255
+ def end_session(self, request_id: str) -> None:
256
+ """
257
+ Notify LMCache server to remove the session for a finished request.
258
+ Args:
259
+ request_id: The ID of the finished request.
260
+ """
261
+ send_lmcache_request(
262
+ self.mq_client,
263
+ RequestType.END_SESSION,
264
+ [request_id],
265
+ )
266
+
267
# Helper functions
163
- def _create_key(self, block_hash: bytes) -> IPCCacheEngineKey:
164
- """Convert a block hash to an IPC cache engine key"""
268
+ def _create_key(
269
+ self,
270
+ token_ids: list[int],
271
+ start: int = 0,
272
+ end: int = 0,
273
+ request_id: str | None = None,
274
+ ) -> IPCCacheEngineKey:
275
+ """Convert token IDs to an IPC cache engine key"""
276
return IPCCacheEngineKey(
277
model_name=self.model_name,
278
world_size=self.world_size,
279
worker_id=self.worker_id,
169
- chunk_hash=block_hash,
280
+ token_ids=tuple(token_ids),
281
+ start=start,
282
+ end=end,
283
+ request_id=request_id,
284
+ )
285
+
286
+ def _create_hash_key(
287
+ self, chunk_hash: bytes, request_id: str | None = None
288
+ ) -> IPCCacheEngineKey:
289
+ """Create a hash-mode IPC cache engine key"""
290
+ return IPCCacheEngineKey(
291
+ model_name=self.model_name,
292
+ world_size=self.world_size,
293
+ worker_id=None,
294
+ chunk_hash=chunk_hash,
295
+ request_id=request_id,
296
)
297
298
@@ -180,10 +306,6 @@ class LMCacheMPWorkerAdapter:
306
kv_rank: int,
307
vllm_block_size: int,
308
):
183
- logger.warning(
184
- "Importing LMCacheMPWorkerAdapter is deprecated. "
185
- "Please update your LMCache to the latest version."
186
- )
309
self.mq_client = MessageQueueClient(server_url, context)
310
311
# Instance id for GPU worker
@@ -201,7 +323,10 @@ class LMCacheMPWorkerAdapter:
323
str, tuple[MessagingFuture[RetrieveResult], list[str]]
324
] = {}
325
326
+ # The store requests that have finished execution in LMCache
327
self.finished_stores: set[str] = set()
328
+ # The finished request ids that are passed via vLLM and also
329
+ # have corresponding store requests submitted to LMCache before
330
self.previously_finished: set[str] = set()
331
332
self.model_name = model_name
@@ -215,7 +340,14 @@ class LMCacheMPWorkerAdapter:
340
)
341
self.blocks_in_chunk = chunk_size // vllm_block_size
342
218
- def register_kv_caches(self, kv_caches: dict[str, KVCache]):
343
+ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
344
+ """
345
+ Register the kv caches with LMCache server
346
+
347
+ Args:
348
+ kv_caches: A dict of kv caches to register. The keys are the
349
+ layer names and the values are the corresponding tensors.
350
+ """
351
# Register kv cache and send the request
352
self.kv_caches = kv_caches
353
logger.info("Registering kv caches")
@@ -230,7 +362,29 @@ class LMCacheMPWorkerAdapter:
362
def submit_store_request(
363
self, request_id: str, op: LoadStoreOp, event: torch.cuda.Event
364
):
233
- keys = self._block_hashes_to_keys(op.block_hashes)
365
+ """
366
+ Submit a KV cache store request to LMCache
367
+
368
+ Args:
369
+ request_id: The ID of the request
370
+ op: The LoadStoreOp describing the store operation.
371
+ event: The CUDA event that is recorded after the current
372
+ model inference step
373
+ """
374
+ if op.block_hashes is not None:
375
+ # Hash mode
376
+ chunk_hashes = list(
377
+ striding_block_hashes(op.block_hashes, self.blocks_in_chunk)
378
+ )
379
+ keys = [
380
+ self._create_hash_key(ch, request_id=request_id) for ch in chunk_hashes
381
+ ]
382
+ else:
383
+ # Token mode
384
+ assert op.token_ids is not None
385
+ keys = [
386
+ self._create_key(op.token_ids, op.start, op.end, request_id=request_id)
387
+ ]
388
future = send_lmcache_request(
389
self.mq_client,
390
RequestType.STORE,
@@ -242,7 +396,29 @@ class LMCacheMPWorkerAdapter:
396
def submit_retrieve_request(
397
self, request_id: str, op: LoadStoreOp, event: torch.cuda.Event
398
):
245
- keys = self._block_hashes_to_keys(op.block_hashes)
399
+ """
400
+ Submit a KV cache retrieve request to LMCache
401
+
402
+ Args:
403
+ request_id: The ID of the request
404
+ op: The LoadStoreOp describing the retrieve operation.
405
+ event: The CUDA event that is recorded after the current
406
+ model inference step
407
+ """
408
+ if op.block_hashes is not None:
409
+ # Hash mode
410
+ chunk_hashes = list(
411
+ striding_block_hashes(op.block_hashes, self.blocks_in_chunk)
412
+ )
413
+ keys = [
414
+ self._create_hash_key(ch, request_id=request_id) for ch in chunk_hashes
415
+ ]
416
+ else:
417
+ # Token mode
418
+ assert op.token_ids is not None
419
+ keys = [
420
+ self._create_key(op.token_ids, op.start, op.end, request_id=request_id)
421
+ ]
422
future = send_lmcache_request(
423
self.mq_client,
424
RequestType.RETRIEVE,
@@ -257,17 +433,47 @@ class LMCacheMPWorkerAdapter:
433
ops: list[LoadStoreOp],
434
event: torch.cuda.Event,
435
):
260
- keys = []
261
- block_ids = []
262
- for op in ops:
263
- keys.extend(self._block_hashes_to_keys(op.block_hashes))
436
+ """
437
+ Submit a batched store request to LMCache
438
+
439
+ Args:
440
+ request_ids: The IDs of the requests
441
+ ops: The LoadStoreOps describing the store operations. Should have
442
+ the same length as request_ids
443
+ event: The CUDA event that is recorded after the current
444
+ model inference step
445
+ """
446
+ all_keys: list[IPCCacheEngineKey] = []
447
+ block_ids: list[int] = []
448
+ for request_id, op in zip(request_ids, ops, strict=False):
449
+ if op.block_hashes is not None:
450
+ chunk_hashes = list(
451
+ striding_block_hashes(op.block_hashes, self.blocks_in_chunk)
452
+ )
453
+ keys = [
454
+ self._create_hash_key(ch, request_id=request_id)
455
+ for ch in chunk_hashes
456
+ ]
457
+ all_keys.extend(keys)
458
+ else:
459
+ assert op.token_ids is not None
460
+ all_keys.append(
461
+ self._create_key(
462
+ op.token_ids, op.start, op.end, request_id=request_id
463
+ )
464
+ )
465
block_ids.extend(op.block_ids)
466
future = send_lmcache_request(
467
self.mq_client,
468
RequestType.STORE,
268
- [keys, self.instance_id, block_ids, event.ipc_handle()],
469
+ [
470
+ all_keys,
471
+ self.instance_id,
472
+ block_ids,
473
+ event.ipc_handle(),
474
+ ],
475
).to_cuda_future()
270
- self.store_futures[request_ids[0]] = (future, request_ids[1:])
476
+ self.store_futures[request_ids[0]] = (future, list(request_ids[1:]))
477
478
@_lmcache_nvtx_annotate
479
def batched_submit_retrieve_requests(
@@ -276,34 +482,83 @@ class LMCacheMPWorkerAdapter:
482
ops: list[LoadStoreOp],
483
event: torch.cuda.Event,
484
):
279
- keys = []
280
- block_ids = []
485
+ """
486
+ Submit a batched retrieve request to LMCache
487
282
- for op in ops:
283
- keys.extend(self._block_hashes_to_keys(op.block_hashes))
488
+ Args:
489
+ request_ids: The IDs of the requests
490
+ ops: The LoadStoreOps describing the retrieve operations. Should have
491
+ the same length as request_ids
492
+ event: The CUDA event that is recorded after the current
493
+ model inference step
494
+ """
495
+ all_keys: list[IPCCacheEngineKey] = []
496
+ block_ids: list[int] = []
497
+ for request_id, op in zip(request_ids, ops, strict=False):
498
+ if op.block_hashes is not None:
499
+ chunk_hashes = list(
500
+ striding_block_hashes(op.block_hashes, self.blocks_in_chunk)
501
+ )
502
+ keys = [
503
+ self._create_hash_key(ch, request_id=request_id)
504
+ for ch in chunk_hashes
505
+ ]
506
+ all_keys.extend(keys)
507
+ else:
508
+ assert op.token_ids is not None
509
+ all_keys.append(
510
+ self._create_key(
511
+ op.token_ids, op.start, op.end, request_id=request_id
512
+ )
513
+ )
514
block_ids.extend(op.block_ids)
515
future = send_lmcache_request(
516
self.mq_client,
517
RequestType.RETRIEVE,
288
- [keys, self.instance_id, block_ids, event.ipc_handle()],
518
+ [
519
+ all_keys,
520
+ self.instance_id,
521
+ block_ids,
522
+ event.ipc_handle(),
523
+ ],
524
).to_cuda_future()
290
- self.retrieve_futures[request_ids[0]] = (future, request_ids[1:])
525
+ self.retrieve_futures[request_ids[0]] = (future, list(request_ids[1:]))
526
527
@_lmcache_nvtx_annotate
528
def get_finished(
294
- self, finished_req_ids: set[str]
529
+ self, finished_req_ids_from_engine: set[str]
530
) -> tuple[set[str] | None, set[str] | None]:
531
+ """
532
+ Check and get the finished store and retrieve requests.
533
+
534
+ Args:
535
+ finished_req_ids_from_engine: the set of request ids that are
536
+ reported as finished from the vLLM engine side.
537
+
538
+ Returns:
539
+ A tuple of two sets:
540
+ - The first set contains the finished store request ids. The returned
541
+ store request ids MUST be seen before in the
542
+ `finished_req_ids_from_engine`.
543
+ - The second set contains the finished retrieve request ids.
544
+
545
+ Notes:
546
+ When enabling async scheduling in vLLM, the same request ID may appear
547
+ multiple times in `finished_req_ids_from_engine`. The adapter should
548
+ take care of deduplicating the request IDs and only return the request
549
+ IDs that have not been returned before.
550
+ """
551
finished_stores = set()
552
finished_retrieves = set()
298
- for request_id, (future, other_reqs) in self.store_futures.items():
299
- if not future.query():
553
+ for request_id, (s_future, other_reqs) in self.store_futures.items():
554
+ if not s_future.query():
555
continue
556
302
- result = future.result()
557
+ s_result = s_future.result()
558
finished_stores.add(request_id)
559
finished_stores.update(other_reqs)
560
306
- if not result:
561
+ if not s_result:
562
# TODO: add error handling here
563
logger.error(
564
"Something went wrong when processing the "
@@ -311,21 +566,21 @@ class LMCacheMPWorkerAdapter:
566
request_id,
567
)
568
314
- for request_id, (future, other_reqs) in self.retrieve_futures.items():
315
- if not future.query():
569
+ for request_id, (r_future, other_reqs) in self.retrieve_futures.items():
570
+ if not r_future.query():
571
continue
572
318
- result = future.result()
573
+ r_result = r_future.result()
574
finished_retrieves.add(request_id)
575
finished_retrieves.update(other_reqs)
576
322
- if not all(result):
577
+ if not all(r_result):
578
# TODO: add error handing here
579
logger.error(
580
"Something went wrong when processing the "
581
"retrieve request for request_id=%s, result=%s",
582
request_id,
328
- result,
583
+ r_result,
584
)
585
586
# Remove the finished requests from the tracking dicts
@@ -338,7 +593,7 @@ class LMCacheMPWorkerAdapter:
593
self.finished_stores.update(finished_stores)
594
595
ret_stores = set()
341
- for req_id in finished_req_ids:
596
+ for req_id in finished_req_ids_from_engine:
597
if req_id in self.finished_stores or req_id in self.store_futures:
598
self.previously_finished.add(req_id)
599
else:
@@ -357,7 +612,9 @@ class LMCacheMPWorkerAdapter:
612
return self.blocks_in_chunk
613
614
def shutdown(self):
360
- # Unregister kv cache
615
+ """
616
+ Shutdown the LMCache MP worker adapter
617
+ """
618
logger.info("Unregistering kv caches")
619
send_lmcache_request(
620
self.mq_client, RequestType.UNREGISTER_KV_CACHE, [self.instance_id]
@@ -378,18 +635,32 @@ class LMCacheMPWorkerAdapter:
635
636
return safe_finished_s
637
381
- def _create_key(self, block_hash: bytes) -> IPCCacheEngineKey:
382
- """Convert a block hash to an IPC cache engine key"""
638
+ def _create_key(
639
+ self,
640
+ token_ids: list[int],
641
+ start: int = 0,
642
+ end: int = 0,
643
+ request_id: str | None = None,
644
+ ) -> IPCCacheEngineKey:
645
+ """Convert token IDs to an IPC cache engine key"""
646
return IPCCacheEngineKey(
647
model_name=self.model_name,
648
world_size=self.world_size,
649
worker_id=self.worker_id,
387
- chunk_hash=block_hash,
650
+ token_ids=tuple(token_ids),
651
+ start=start,
652
+ end=end,
653
+ request_id=request_id,
654
)
655
390
- def _block_hashes_to_keys(
391
- self, block_hashes: list[bytes]
392
- ) -> list[IPCCacheEngineKey]:
393
- """Convert block hashes to IPC cache engine keys"""
394
- s = striding_block_hashes(block_hashes, self.blocks_in_chunk)
395
- return [self._create_key(block_hash) for block_hash in s]
656
+ def _create_hash_key(
657
+ self, chunk_hash: bytes, request_id: str | None = None
658
+ ) -> IPCCacheEngineKey:
659
+ """Create a hash-mode IPC cache engine key"""
660
+ return IPCCacheEngineKey(
661
+ model_name=self.model_name,
662
+ world_size=self.world_size,
663
+ worker_id=self.worker_id,
664
+ chunk_hash=chunk_hash,
665
+ request_id=request_id,
666
+ )
vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py
+32
-17
@@ -3,7 +3,7 @@
3
import enum
4
from collections.abc import Iterable
5
from dataclasses import dataclass, field
6
-from typing import TYPE_CHECKING, Any, Literal, cast
6
+from typing import TYPE_CHECKING, Any, Literal
7
8
import torch
9
import zmq
@@ -130,12 +130,6 @@ def create_worker_adapter(
130
)
131
132
133
-def convert_block_hashes_to_bytes(
134
- block_hashes: list["BlockHash"],
135
-) -> list[bytes]:
136
- return cast(list[bytes], block_hashes)
137
-
138
-
133
class LMCacheMPRequestState(enum.Enum):
134
"""
135
State machine:
@@ -266,6 +260,7 @@ class LMCacheMPRequestMetadata:
260
Args:
261
tracker: The request tracker to generate the metadata from.
262
blocks_in_chunk: the number of blocks in a LMCache data chunk
263
+ vllm_block_size: the block size used in vLLM
264
"""
265
# Store the blocks that has block hashes
266
# NOTE: the invariant here is that `num_stored_blocks` should
@@ -282,15 +277,21 @@ class LMCacheMPRequestMetadata:
277
if num_chunks >= 1:
278
start = tracker.num_stored_blocks
279
end = start + num_chunks * blocks_in_chunk
285
- block_hashes = convert_block_hashes_to_bytes(
286
- tracker.block_hashes[start:end]
287
- )
280
block_ids = tracker.allocated_block_ids[start:end]
281
+ start_token_idx = start * vllm_block_size
282
+ end_token_idx = end * vllm_block_size
283
+ token_ids = list(tracker.all_token_ids)
284
+ op = LoadStoreOp(
285
+ token_ids=token_ids,
286
+ block_ids=block_ids,
287
+ start=start_token_idx,
288
+ end=end_token_idx,
289
+ )
290
291
ret = LMCacheMPRequestMetadata(
292
request_id=tracker.request_id,
293
direction="STORE",
293
- op=LoadStoreOp(block_hashes=block_hashes, block_ids=block_ids),
294
+ op=op,
295
)
296
297
# Update the request tracker
@@ -303,6 +304,7 @@ class LMCacheMPRequestMetadata:
304
def GetRetrieveMetadata(
305
tracker: LMCacheMPRequestTracker,
306
blocks_in_chunk: int,
307
+ vllm_block_size: int,
308
) -> "LMCacheMPRequestMetadata | None":
309
"""
310
Generate the retrieve metadata for the current request tracker.
@@ -310,6 +312,7 @@ class LMCacheMPRequestMetadata:
312
Args:
313
tracker: The request tracker to generate the metadata from.
314
blocks_in_chunk: the number of blocks in a LMCache data chunk
315
+ vllm_block_size: the block size used in vLLM
316
"""
317
if not tracker.is_ready_for_retrieving():
318
return None
@@ -330,15 +333,21 @@ class LMCacheMPRequestMetadata:
333
"number of LMCache hit blocks. "
334
)
335
if end > start:
333
- block_hashes = convert_block_hashes_to_bytes(
334
- tracker.block_hashes[start:end]
335
- )
336
block_ids = tracker.allocated_block_ids[start:end]
337
+ start_token_idx = start * vllm_block_size
338
+ end_token_idx = end * vllm_block_size
339
+ token_ids = list(tracker.all_token_ids)
340
+ op = LoadStoreOp(
341
+ token_ids=token_ids,
342
+ block_ids=block_ids,
343
+ start=start_token_idx,
344
+ end=end_token_idx,
345
+ )
346
347
ret = LMCacheMPRequestMetadata(
348
request_id=tracker.request_id,
349
direction="RETRIEVE",
341
- op=LoadStoreOp(block_hashes=block_hashes, block_ids=block_ids),
350
+ op=op,
351
)
352
return ret
353
@@ -643,7 +652,8 @@ class LMCacheMPConnector(KVConnectorBase_V1):
652
return 0, False
653
654
self.scheduler_adapter.maybe_submit_lookup_request(
646
- request.request_id, convert_block_hashes_to_bytes(request.block_hashes)
655
+ request.request_id,
656
+ token_ids=list(request.all_token_ids),
657
)
658
659
ret = self.scheduler_adapter.check_lookup_result(request.request_id)
@@ -766,6 +776,9 @@ class LMCacheMPConnector(KVConnectorBase_V1):
776
"""
777
# Clean up request tracker to prevent memory leak
778
self._cleanup_request_tracker(request.request_id)
779
+ # Notify LMCache to end the session for this request
780
+ self.scheduler_adapter.end_session(request.request_id)
781
+
782
return True, None
783
784
def take_events(self) -> Iterable["KVCacheEvent"]:
@@ -846,7 +859,9 @@ class LMCacheMPConnector(KVConnectorBase_V1):
859
if request_tracker.state != LMCacheMPRequestState.WAITING_FOR_LOAD:
860
continue
861
r_metadata = LMCacheMPRequestMetadata.GetRetrieveMetadata(
849
- request_tracker, blocks_per_chunk
862
+ request_tracker,
863
+ blocks_per_chunk,
864
+ vllm_block_size=self.vllm_block_size,
865
)
866
if r_metadata is not None:
867
metadata.add_request_metadata(r_metadata)