lmcache-cli 0.5.3.dev0__py3-none-any.whl → 0.5.3rc2.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. lmcache/__init__.py +30 -10
  2. lmcache/_version.py +3 -3
  3. lmcache/cli/main.py +4 -0
  4. lmcache/integration/sglang/multi_process_adapter.py +95 -2
  5. lmcache/integration/sglang/sglang_adapter.py +21 -14
  6. lmcache/integration/sglang/utils.py +1 -1
  7. lmcache/integration/vllm/experimental/__init__.py +12 -0
  8. lmcache/integration/vllm/experimental/dispatcher.py +213 -0
  9. lmcache/integration/vllm/kv_cache_group_edits.py +157 -7
  10. lmcache/integration/vllm/lmcache_mp_connector.py +91 -16
  11. lmcache/integration/vllm/lmcache_mp_connector_0201.py +5 -5
  12. lmcache/integration/vllm/utils.py +24 -0
  13. lmcache/integration/vllm/vllm_multi_process_adapter.py +67 -78
  14. lmcache/lmcache_frontend/app.py +14 -2
  15. lmcache/lmcache_frontend/heartbeat.py +27 -3
  16. lmcache/lmcache_frontend/run_mp_server_with_frontend.sh +31 -6
  17. lmcache/sdk/__init__.py +38 -5
  18. lmcache/sdk/batch.py +57 -45
  19. lmcache/sdk/cache_kind.py +50 -0
  20. lmcache/sdk/context.py +533 -0
  21. lmcache/sdk/kvcache.py +10 -507
  22. lmcache/sdk/qcache.py +40 -0
  23. lmcache/sdk/qringbuffer.py +765 -0
  24. lmcache/sdk/{stream.py → request.py} +84 -71
  25. lmcache/sdk/wrapper/contiguous.py +11 -5
  26. lmcache/tools/simple_discover_service.py +22 -1
  27. lmcache/v1/distributed/api.py +51 -0
  28. lmcache/v1/distributed/internal_api.py +14 -1
  29. lmcache/v1/distributed/l1_manager.py +29 -7
  30. lmcache/v1/distributed/l2_adapters/base.py +43 -0
  31. lmcache/v1/distributed/l2_adapters/dax_l2_adapter.py +45 -4
  32. lmcache/v1/distributed/l2_adapters/fault_inject_l2_adapter.py +5 -0
  33. lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +79 -9
  34. lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +21 -11
  35. lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py +7 -5
  36. lmcache/v1/distributed/l2_adapters/sagemaker_hyperpod_client.py +92 -12
  37. lmcache/v1/distributed/l2_adapters/serde_wrapper.py +11 -2
  38. lmcache/v1/distributed/memory_manager/devdax_l1_memory_manager.py +88 -1
  39. lmcache/v1/distributed/memory_manager/gds_l1_memory_manager.py +12 -1
  40. lmcache/v1/distributed/memory_manager/l1_manager_protocol.py +5 -1
  41. lmcache/v1/distributed/memory_manager/l1_memory_manager.py +12 -1
  42. lmcache/v1/distributed/serde/__init__.py +12 -0
  43. lmcache/v1/distributed/serde/aesgcm.py +180 -0
  44. lmcache/v1/distributed/serde/asym_k16_v8.py +9 -4
  45. lmcache/v1/distributed/serde/async_processor.py +10 -5
  46. lmcache/v1/distributed/serde/base.py +13 -3
  47. lmcache/v1/distributed/serde/fp8.py +5 -5
  48. lmcache/v1/distributed/serde/key_provider.py +66 -0
  49. lmcache/v1/distributed/serde/multi.py +11 -7
  50. lmcache/v1/distributed/serde/turboquant/turboquant.py +11 -7
  51. lmcache/v1/distributed/storage_controllers/prefetch_controller.py +21 -71
  52. lmcache/v1/distributed/storage_manager.py +23 -46
  53. lmcache/v1/gpu_connector/gpu_connectors.py +23 -17
  54. lmcache/v1/gpu_connector/hpu_connector.py +6 -3
  55. lmcache/v1/gpu_connector/kv_format/__init__.py +5 -1
  56. lmcache/v1/gpu_connector/kv_format/contiguity.py +24 -3
  57. lmcache/v1/gpu_connector/kv_format/detection.py +25 -0
  58. lmcache/v1/gpu_connector/kv_format/detectors/vllm.py +9 -13
  59. lmcache/v1/gpu_connector/kv_format/specs/base.py +10 -0
  60. lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bs_nh_cs.py +68 -0
  61. lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bs_nh_two_hs.py +14 -3
  62. lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bsv_bss.py +23 -0
  63. lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_nh_bs_cs.py +67 -0
  64. lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_nh_bs_two_hs.py +14 -3
  65. lmcache/v1/gpu_connector/musa_connectors.py +7 -4
  66. lmcache/v1/gpu_connector/utils.py +14 -7
  67. lmcache/v1/gpu_connector/xpu_connectors.py +12 -9
  68. lmcache/v1/kv_layer_groups.py +1 -1
  69. lmcache/v1/memory_allocators/devdax_memory_allocator.py +496 -157
  70. lmcache/v1/metadata.py +6 -6
  71. lmcache/v1/mp_coordinator/api.py +116 -0
  72. lmcache/v1/mp_coordinator/app.py +5 -2
  73. lmcache/v1/mp_coordinator/blend_client.py +19 -18
  74. lmcache/v1/mp_coordinator/cache_control/eviction_manager.py +8 -4
  75. lmcache/v1/mp_coordinator/cache_control/prefetch_manager.py +8 -5
  76. lmcache/v1/mp_coordinator/cache_control/resync_manager.py +9 -3
  77. lmcache/v1/mp_coordinator/cache_events.py +324 -0
  78. lmcache/v1/mp_coordinator/http_apis/cache_api.py +1 -1
  79. lmcache/v1/mp_coordinator/http_apis/dependencies.py +4 -0
  80. lmcache/v1/mp_coordinator/http_apis/directory_api.py +137 -0
  81. lmcache/v1/mp_coordinator/http_apis/quota_api.py +1 -1
  82. lmcache/v1/mp_coordinator/key_directory.py +328 -0
  83. lmcache/v1/mp_coordinator/schemas.py +119 -1
  84. lmcache/v1/mp_observability/README.md +4 -0
  85. lmcache/v1/mp_observability/config.py +41 -0
  86. lmcache/v1/mp_observability/event.py +6 -0
  87. lmcache/v1/mp_observability/gc_monitor.py +186 -0
  88. lmcache/v1/mp_observability/trace/codecs.py +33 -0
  89. lmcache/v1/multiprocess/cache_control/object_service.py +1 -2
  90. lmcache/v1/multiprocess/cache_control/prefetch_service.py +1 -1
  91. lmcache/v1/multiprocess/config.py +45 -29
  92. lmcache/v1/multiprocess/custom_types.py +1 -1
  93. lmcache/v1/multiprocess/futures.py +55 -35
  94. lmcache/v1/multiprocess/http_apis/cache_api.py +2 -1
  95. lmcache/v1/multiprocess/http_apis/schemas.py +1 -2
  96. lmcache/v1/multiprocess/http_server.py +38 -7
  97. lmcache/v1/multiprocess/modules/blend.py +2 -2
  98. lmcache/v1/multiprocess/modules/blend_v3.py +823 -107
  99. lmcache/v1/multiprocess/modules/experimental/__init__.py +10 -0
  100. lmcache/v1/multiprocess/modules/experimental/qstore.py +551 -0
  101. lmcache/v1/multiprocess/modules/lmcache_driven_transfer.py +56 -39
  102. lmcache/v1/multiprocess/modules/lookup.py +7 -4
  103. lmcache/v1/multiprocess/modules/management.py +19 -0
  104. lmcache/v1/multiprocess/modules/p2p_controller.py +18 -13
  105. lmcache/v1/multiprocess/native_completion.py +32 -29
  106. lmcache/v1/multiprocess/protocols/README.md +69 -24
  107. lmcache/v1/multiprocess/protocols/base.py +6 -0
  108. lmcache/v1/multiprocess/protocols/blend_v3.py +14 -3
  109. lmcache/v1/multiprocess/protocols/controller.py +11 -0
  110. lmcache/v1/multiprocess/protocols/engine.py +37 -0
  111. lmcache/v1/multiprocess/protocols/p2p.py +3 -2
  112. lmcache/v1/multiprocess/server.py +46 -3
  113. lmcache/v1/multiprocess/transfer_context/worker_transfer.py +259 -23
  114. lmcache/v1/multiprocess/warm_prefetch.py +7 -4
  115. lmcache/v1/periodic_thread.py +24 -2
  116. lmcache/v1/platform/__init__.py +68 -93
  117. lmcache/v1/platform/_device_detect.py +167 -0
  118. lmcache/v1/platform/base/__init__.py +9 -0
  119. lmcache/v1/platform/{base_cache_context.py → base/cache_context.py} +4 -4
  120. lmcache/v1/platform/base/device_ops.py +242 -0
  121. lmcache/v1/platform/{base_device_spec.py → base/device_spec.py} +72 -7
  122. lmcache/v1/platform/base/event_ipc.py +274 -0
  123. lmcache/v1/platform/{base_ipc_wrapper.py → base/ipc_wrapper.py} +2 -2
  124. lmcache/v1/platform/cache_context.py +25 -115
  125. lmcache/v1/platform/cpu/__init__.py +46 -14
  126. lmcache/v1/platform/cpu/cache_context.py +11 -16
  127. lmcache/v1/platform/cpu/device_ops.py +19 -0
  128. lmcache/v1/platform/cpu/shm.py +9 -8
  129. lmcache/v1/platform/cuda/__init__.py +38 -6
  130. lmcache/v1/platform/cuda/cache_context.py +2 -2
  131. lmcache/v1/platform/cuda/device_ops.py +39 -0
  132. lmcache/v1/platform/cuda/ipc_wrapper.py +1 -1
  133. lmcache/v1/platform/cuda/pin_memory.py +1 -1
  134. lmcache/v1/platform/hpu/__init__.py +8 -3
  135. lmcache/v1/platform/hpu/device_ops.py +19 -0
  136. lmcache/v1/platform/kv_wrap.py +97 -0
  137. lmcache/v1/platform/musa/__init__.py +23 -4
  138. lmcache/v1/platform/musa/device_ops.py +85 -0
  139. lmcache/v1/platform/musa/event_ipc.py +121 -0
  140. lmcache/v1/platform/musa/ipc_wrapper.py +203 -153
  141. lmcache/v1/platform/musa/native_kv_transfer.py +7 -7
  142. lmcache/v1/platform/ops_types.py +208 -0
  143. lmcache/{python_ops_fallback.py → v1/platform/torch_ops.py} +45 -204
  144. lmcache/v1/platform/xpu/__init__.py +8 -3
  145. lmcache/v1/platform/xpu/device_ops.py +39 -0
  146. lmcache/v1/plugin/runtime_plugin_launcher.py +16 -8
  147. lmcache/v1/storage_backend/local_cpu_backend.py +2 -2
  148. lmcache/v1/storage_backend/pd_backend_async.py +16 -0
  149. lmcache/v1/storage_backend/plugins/dax_backend.py +14 -8
  150. lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +9 -4
  151. lmcache/v1/storage_backend/raw_block/core.py +15 -31
  152. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/METADATA +4 -1
  153. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/RECORD +158 -130
  154. lmcache/v1/distributed/tiers.py +0 -23
  155. lmcache/v1/platform/musa/ops.py +0 -91
  156. /lmcache/v1/platform/{base_pin_memory.py → base/pin_memory.py} +0 -0
  157. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/WHEEL +0 -0
  158. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/entry_points.txt +0 -0
  159. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/licenses/LICENSE +0 -0
  160. {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/top_level.txt +0 -0
lmcache/__init__.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Standard
4
4
  import sys
5
+ import types
5
6
 
6
7
  # First Party
7
8
  from lmcache.logging import init_logger
@@ -9,7 +10,6 @@ from lmcache.logging import init_logger
9
10
  # --------------------------
10
11
  # Backend instance & Device detection
11
12
  # --------------------------
12
- from lmcache.v1.platform import get_backend
13
13
  from lmcache.v1.platform import torch_dev as torch_dev
14
14
  from lmcache.v1.platform import torch_device_type as torch_device_type
15
15
 
@@ -23,14 +23,34 @@ logger = init_logger(__name__)
23
23
 
24
24
  __all__ = ["__version__", "torch_dev", "torch_device_type"]
25
25
 
26
- _ops = get_backend(torch_device_type)
27
- if _ops is not None:
28
- # Override lmcache.c_ops with merged module,
29
- # in which:
30
- # python_ops_fallback as base,
31
- # use backend implementation if exists
32
- sys.modules["lmcache.c_ops"] = _ops
33
- else:
26
+
27
+ # --------------------------
28
+ # Backward-compat ``lmcache.c_ops`` shim
29
+ # --------------------------
30
+ def _install_c_ops_shim() -> None:
31
+ """Register ``lmcache.c_ops`` as the resolved :class:`DeviceOps` instance.
32
+
33
+ Resolves the singleton :class:`DeviceOps` instance for the detected
34
+ device (which calls :meth:`ensure_native` internally), then registers
35
+ a PEP 562 shim module that forwards attribute access to it.
36
+ """
37
+ # First Party
38
+ from lmcache.v1.platform import resolve_device_ops
39
+
40
+ ops = resolve_device_ops(torch_device_type)
41
+
42
+ shim = types.ModuleType("lmcache.c_ops")
43
+ shim.__getattr__ = lambda name: getattr(ops, name) # type: ignore[method-assign]
44
+ shim.__dir__ = lambda: dir(ops) # type: ignore[method-assign]
45
+ sys.modules["lmcache.c_ops"] = shim
46
+ globals()["c_ops"] = shim # parent attr for IMPORT_FROM bytecode
47
+
48
+
49
+ try:
50
+ _install_c_ops_shim()
51
+ except Exception as exc:
34
52
  logger.warning(
35
- "No compute backend loaded; CLI-only mode (torch/numba not installed)"
53
+ "No compute backend loaded; CLI-only mode (torch/numba not installed). "
54
+ "Reason: %s",
55
+ exc,
36
56
  )
lmcache/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.5.3.dev0'
22
- __version_tuple__ = version_tuple = (0, 5, 3, 'dev0')
21
+ __version__ = version = '0.5.3rc2.dev0'
22
+ __version_tuple__ = version_tuple = (0, 5, 3, 'rc2', 'dev0')
23
23
 
24
- __commit_id__ = commit_id = 'gcd2c0d6a6'
24
+ __commit_id__ = commit_id = 'g0427938a4'
lmcache/cli/main.py CHANGED
@@ -42,3 +42,7 @@ def main() -> None:
42
42
  except Exception: # noqa: BLE001
43
43
  logger.exception("Command failed")
44
44
  sys.exit(1)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -34,6 +34,7 @@ from lmcache.v1.multiprocess.custom_types import (
34
34
  IPCCacheServerKey,
35
35
  KVCache,
36
36
  )
37
+ from lmcache.v1.multiprocess.futures import MessagingFuture
37
38
  from lmcache.v1.multiprocess.mq import MessageQueueClient
38
39
  from lmcache.v1.multiprocess.protocol import RequestType
39
40
  from lmcache.v1.platform.cuda.ipc_wrapper import CudaIPCWrapper
@@ -62,6 +63,28 @@ def _wrap_sglang_kv_caches(
62
63
  return wrapped
63
64
 
64
65
 
66
+ def _completed_future(result: bool) -> MessagingFuture[bool]:
67
+ """Return an already-completed future resolving to ``result``.
68
+
69
+ Used by :meth:`LMCacheMPConnector.store_kv_async` for the paths that
70
+ perform no wire send, so every return value is a pollable future and
71
+ callers never have to special-case ``None``. ``result`` carries the
72
+ store outcome for that path: ``False`` when the connector is
73
+ unhealthy (nothing was stored), ``True`` when there was simply no
74
+ chunk-aligned range to store (a no-op success).
75
+
76
+ Args:
77
+ result: the success value the returned future resolves to.
78
+
79
+ Returns:
80
+ A ``MessagingFuture`` whose ``result()`` is immediately
81
+ ``result``.
82
+ """
83
+ future: MessagingFuture[bool] = MessagingFuture()
84
+ future.set_result(result)
85
+ return future
86
+
87
+
65
88
  @dataclass
66
89
  class _PendingLookup:
67
90
  """Per-request_id state retained between ``lookup_kv`` and
@@ -409,7 +432,7 @@ class LMCacheMPConnector:
409
432
  event.ipc_handle(),
410
433
  skip_prefix_n_blocks,
411
434
  ],
412
- ).to_cuda_future(device=self.device)
435
+ ).to_device_future(device=self.device)
413
436
 
414
437
  def retrieve_kv(self, load_metadata: LoadMetadata) -> int:
415
438
  """Phase 2 of the two-phase load — fires RETRIEVE only.
@@ -491,6 +514,76 @@ class LMCacheMPConnector:
491
514
  self._pending_lookups[request_id].locks_held = False
492
515
  return retrieve_token_num - offset
493
516
 
517
+ def store_kv_async(self, store_metadata: StoreMetadata) -> MessagingFuture[bool]:
518
+ """Submit a STORE and return its completion future without waiting.
519
+
520
+ Fires the STORE request for the chunk-aligned prefix of
521
+ ``store_metadata`` and returns immediately with a future the
522
+ caller can poll (``query`` / ``wait``) or block on (``result``)
523
+ at a later, deferred checkpoint. The future resolves to a
524
+ ``bool`` success flag once the daemon finishes copying the KV
525
+ slots GPU → warehouse.
526
+
527
+ The KV slots referenced by ``store_metadata`` must remain pinned
528
+ (not evicted or reused) until the returned future reports done;
529
+ the caller owns that lifetime. Paths that perform no wire send
530
+ return an already-completed future so callers never special-case
531
+ ``None``: an unhealthy connector resolves to ``False`` (nothing
532
+ was stored), and no chunk-aligned range resolves to ``True`` (a
533
+ no-op success).
534
+
535
+ END_SESSION is owned by ``LMCRadixCache.cache_finished_req``
536
+ (see :meth:`end_session`); it is not fired here.
537
+
538
+ Args:
539
+ store_metadata: tokens, request id, and KV slot indices for
540
+ the finished request.
541
+
542
+ Returns:
543
+ A future resolving to ``True`` when the store completes
544
+ successfully (or there was nothing to store), or ``False``
545
+ on daemon-side failure or an unhealthy connector.
546
+ """
547
+ if not self.is_healthy:
548
+ return _completed_future(False)
549
+
550
+ aligned_end = (len(store_metadata.token_ids) // self._lmcache_chunk_size) * (
551
+ self._lmcache_chunk_size
552
+ )
553
+ if aligned_end == 0:
554
+ return _completed_future(True)
555
+
556
+ request_id = store_metadata.request_id
557
+ block_ids = self._slot_mapping_to_block_ids(
558
+ store_metadata.kv_indices[:aligned_end]
559
+ )
560
+ event = torch_dev.Event(interprocess=True)
561
+ event.record(torch_dev.current_stream())
562
+ future = send_lmcache_request(
563
+ self.mq_client,
564
+ RequestType.STORE,
565
+ [
566
+ self._create_key(
567
+ store_metadata.token_ids,
568
+ start=0,
569
+ end=aligned_end,
570
+ request_id=request_id,
571
+ ),
572
+ self.instance_id,
573
+ # STORE takes per-group block IDs (list[list[int]]); SGLang is
574
+ # non-hybrid, so wrap the flat list as a single group.
575
+ [block_ids],
576
+ event.ipc_handle(),
577
+ ],
578
+ ).to_cuda_future(device=self.device)
579
+ # Keep the exporting CUDA event alive until the caller releases the
580
+ # future. Since we return without blocking, the local ``event`` would
581
+ # otherwise be garbage-collected immediately, destroying the underlying
582
+ # CUDA event before the daemon imports its IPC handle and waits on it.
583
+ # (Dynamic keepalive attribute; the future type doesn't declare it.)
584
+ future._export_event = event # type: ignore[attr-defined]
585
+ return future
586
+
494
587
  def store_kv(self, store_metadata: StoreMetadata) -> None:
495
588
  if not self.is_healthy:
496
589
  return
@@ -525,7 +618,7 @@ class LMCacheMPConnector:
525
618
  event.ipc_handle(),
526
619
  ],
527
620
  )
528
- .to_cuda_future(device=self.device)
621
+ .to_device_future(device=self.device)
529
622
  .result(timeout=self._mq_timeout)
530
623
  )
531
624
  # END_SESSION is owned by ``LMCRadixCache.cache_finished_req`` so
@@ -286,8 +286,10 @@ class LMCacheLayerwiseConnector(LMCacheConnector):
286
286
  if retrieve_token_num <= offset:
287
287
  self.lmcache_engine.lookup_unpin(lookup_id)
288
288
  logger.info(
289
- f"LMCache retrieve skipped: lookup={retrieve_token_num}, "
290
- f"offset={offset}, no new tokens to retrieve"
289
+ "LMCache retrieve skipped: lookup=%d, "
290
+ "offset=%d, no new tokens to retrieve",
291
+ retrieve_token_num,
292
+ offset,
291
293
  )
292
294
  return 0
293
295
 
@@ -310,8 +312,10 @@ class LMCacheLayerwiseConnector(LMCacheConnector):
310
312
 
311
313
  num_new_tokens = retrieve_token_num - offset
312
314
  logger.info(
313
- f"LMCache retrieve started: lookup={retrieve_token_num}, "
314
- f"offset={offset}, retrieve {num_new_tokens} new tokens"
315
+ "LMCache retrieve started: lookup=%d, offset=%d, retrieve %d new tokens",
316
+ retrieve_token_num,
317
+ offset,
318
+ num_new_tokens,
315
319
  )
316
320
 
317
321
  return num_new_tokens
@@ -324,9 +328,10 @@ class LMCacheLayerwiseConnector(LMCacheConnector):
324
328
  store_mask = torch.ones_like(token_ids, dtype=torch.bool)
325
329
 
326
330
  logger.info(
327
- f"LMCache store_kv started: tokens={len(token_ids)}, "
328
- f"num_layers={self.sgl_config.num_hidden_layers}, "
329
- f"offset={store_metadata.offset}"
331
+ "LMCache store_kv started: tokens=%d, num_layers=%d, offset=%d",
332
+ len(token_ids),
333
+ self.sgl_config.num_hidden_layers,
334
+ store_metadata.offset,
330
335
  )
331
336
 
332
337
  lookup_id = str(uuid.uuid4())
@@ -357,20 +362,22 @@ class LMCacheLayerwiseConnector(LMCacheConnector):
357
362
  next(layerwise_storer)
358
363
  except StopIteration:
359
364
  logger.error(
360
- (
361
- f"store_layer generator stopped at layer {layer_idx}/"
362
- f"{self.sgl_config.num_hidden_layers}"
363
- )
365
+ "store_layer generator stopped at layer %d/%d",
366
+ layer_idx,
367
+ self.sgl_config.num_hidden_layers,
364
368
  )
365
369
  break
366
370
 
367
371
  self.lmcache_engine.lookup_unpin(lookup_id)
368
- logger.info(f"LMCache store_kv completed: stored {len(token_ids)} tokens")
372
+ logger.info("LMCache store_kv completed: stored %d tokens", len(token_ids))
369
373
  except Exception as e:
370
374
  logger.error(
371
- f"LMCache store_kv failed: {type(e).__name__}: {e}", exc_info=True
375
+ "LMCache store_kv failed: %s: %s",
376
+ type(e).__name__,
377
+ e,
378
+ exc_info=True,
372
379
  )
373
380
  try:
374
381
  self.lmcache_engine.lookup_unpin(lookup_id)
375
382
  except Exception as unpin_err:
376
- logger.error(f"Failed to unpin lookup: {unpin_err}", exc_info=True)
383
+ logger.error("Failed to unpin lookup: %s", unpin_err, exc_info=True)
@@ -23,7 +23,7 @@ def lmcache_get_config(config_file: str = "") -> LMCacheEngineConfig:
23
23
  A validated ``LMCacheEngineConfig``.
24
24
  """
25
25
  if config_file:
26
- logger.info(f"Loading LMCache config file {config_file}")
26
+ logger.info("Loading LMCache config file %s", config_file)
27
27
  config = LMCacheEngineConfig.from_file(config_file)
28
28
  else:
29
29
  config = LMCacheEngineConfig.from_defaults()
@@ -0,0 +1,12 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Connector-side experimental tensor-transfer features and their dispatcher."""
3
+
4
+ # First Party
5
+ from lmcache.integration.vllm.experimental.dispatcher import (
6
+ Dispatcher,
7
+ FeatureContext,
8
+ dispatch,
9
+ init_dispatcher,
10
+ )
11
+
12
+ __all__ = ["Dispatcher", "FeatureContext", "dispatch", "init_dispatcher"]
@@ -0,0 +1,213 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Fan-out of connector/adapter lifecycle hooks to active experimental features.
3
+
4
+ The connector and worker adapter know only this dispatcher; the dispatcher
5
+ knows the features; each feature knows the SDK. Adding a tensor type never
6
+ touches the connector or the adapter -- see ``registry.py``.
7
+ """
8
+
9
+ # Future
10
+ from __future__ import annotations
11
+
12
+ # Standard
13
+ from dataclasses import dataclass
14
+ from typing import TYPE_CHECKING, Any, Callable
15
+
16
+ # First Party
17
+ from lmcache.sdk.cache_kind import LMCacheSDKCacheKind
18
+ from lmcache.sdk.qringbuffer import QRingBufferAdapter, QRingBufferCapture
19
+ from lmcache.utils import init_logger
20
+ from lmcache.v1.multiprocess.modules.experimental import TRANSFER_QUERY
21
+
22
+ if TYPE_CHECKING:
23
+ # Third Party
24
+ from vllm.config import VllmConfig
25
+ from vllm.v1.kv_cache_interface import KVCacheConfig
26
+ import torch
27
+
28
+ # First Party
29
+ from lmcache.integration.vllm.lmcache_mp_connector import (
30
+ LMCacheMPConnectorMetadata,
31
+ )
32
+ from lmcache.integration.vllm.vllm_multi_process_adapter import (
33
+ LMCacheMPWorkerAdapter,
34
+ _IpcEvent,
35
+ )
36
+
37
+ logger = init_logger(__name__)
38
+
39
+
40
+ class QTensorFeature:
41
+ """Captures per-layer query tensors into a paged-Q ring and stores them."""
42
+
43
+ def __init__(self, ctx: FeatureContext) -> None:
44
+ self._q_ring_adapter = QRingBufferAdapter(
45
+ adapter=ctx.worker_adapter,
46
+ q_model_name=LMCacheSDKCacheKind.QUERY.server_model_name(
47
+ ctx.worker_adapter.model_name
48
+ ),
49
+ send_lmcache_request=ctx.send_lmcache_request,
50
+ )
51
+ self._capture = QRingBufferCapture(ctx.worker_adapter, self._q_ring_adapter)
52
+
53
+ def register(
54
+ self,
55
+ kv_caches: dict[str, torch.Tensor],
56
+ kv_cache_config: KVCacheConfig | None,
57
+ vllm_config: VllmConfig,
58
+ ) -> None:
59
+ self._capture.setup_q_ring(kv_caches, kv_cache_config, vllm_config)
60
+
61
+ def save_kv_layer(
62
+ self,
63
+ layer_name: str,
64
+ metadata: LMCacheMPConnectorMetadata,
65
+ **layer_io: Any,
66
+ ) -> None:
67
+ self._capture.save_q_layer(layer_name, metadata, **layer_io)
68
+
69
+ def wait_for_save(self, event: _IpcEvent | None) -> None:
70
+ self._capture.batched_submit_qstore_requests(event=event)
71
+
72
+ def reclaim(self) -> None:
73
+ self._q_ring_adapter.reclaim_finished_q_stores()
74
+
75
+ def reregister(self) -> bool:
76
+ try:
77
+ self._q_ring_adapter.reregister_q_ring()
78
+ except ConnectionError:
79
+ logger.exception(
80
+ "Failed to re-register experimental tensors after server "
81
+ "recovery; will retry on next heartbeat"
82
+ )
83
+ return False
84
+ except Exception:
85
+ logger.exception(
86
+ "Unexpected error during experimental tensor "
87
+ "re-registration; will retry on next heartbeat"
88
+ )
89
+ return False
90
+ logger.warning(
91
+ "Finished re-registering experimental tensors after server recovery"
92
+ )
93
+ return True
94
+
95
+ def shutdown(self) -> None:
96
+ self._q_ring_adapter.shutdown_q_ring()
97
+
98
+
99
+ @dataclass
100
+ class FeatureContext:
101
+ """
102
+ Collection of arguments needed for each feature's initialization.
103
+ """
104
+
105
+ worker_adapter: LMCacheMPWorkerAdapter
106
+ send_lmcache_request: Callable[..., Any]
107
+
108
+
109
+ FeatureFactory = Callable[[FeatureContext], "QTensorFeature"]
110
+
111
+ FEATURE_REGISTRY: dict[str, FeatureFactory] = {
112
+ TRANSFER_QUERY: QTensorFeature,
113
+ }
114
+
115
+
116
+ def init_dispatcher(
117
+ ctx: FeatureContext,
118
+ requested: set[str],
119
+ ) -> Dispatcher:
120
+ """Build the dispatcher for intermediate tensors both the server and
121
+ connector agree to transfer.
122
+
123
+ Args:
124
+ ctx: The feature context.
125
+ requested: The set of experimental features from vllm_config.
126
+
127
+ Returns:
128
+ A dispatcher for all experimental features built in the server.
129
+
130
+ Raises:
131
+ ValueError: If a requested capability is not advertised by the server.
132
+ """
133
+ experimental = ctx.worker_adapter.experimental
134
+ features: list[QTensorFeature] = []
135
+ for name, factory in FEATURE_REGISTRY.items():
136
+ if name not in requested:
137
+ continue
138
+ if name not in experimental:
139
+ raise ValueError(f"Connector enables {name} but server does not.")
140
+ features.append(factory(ctx))
141
+ return Dispatcher(features)
142
+
143
+
144
+ def dispatch(
145
+ dispatcher: Dispatcher | None,
146
+ fn_name: str,
147
+ **kwargs: Any,
148
+ ) -> None:
149
+ """Dispatch the given arguments to the dispatcher.
150
+
151
+ Args:
152
+ dispatcher: The dispatcher to dispatch to.
153
+ **kwargs: The arguments to dispatch.
154
+ """
155
+ if dispatcher is None:
156
+ return
157
+ getattr(dispatcher, fn_name)(**kwargs)
158
+
159
+
160
+ class Dispatcher:
161
+ """Holds the experimental features built into the server and connector, and
162
+ fans out lifecycle hooks to all of them. The connector and adapter doesn't
163
+ need to distinguish the types of intermediate tensors. Dispatcher holds the
164
+ TensorFeature which talks to the SDK.
165
+
166
+ Args:
167
+ features: The active features, in fan-out order.
168
+ """
169
+
170
+ def __init__(self, features: list[QTensorFeature]) -> None:
171
+ self._features = features
172
+
173
+ def register(
174
+ self,
175
+ kv_caches: dict[str, torch.Tensor],
176
+ kv_cache_config: KVCacheConfig | None,
177
+ vllm_config: VllmConfig,
178
+ ) -> None:
179
+ """Fan out ``register`` (connector ``register_kv_caches``)."""
180
+ for feature in self._features:
181
+ feature.register(kv_caches, kv_cache_config, vllm_config)
182
+
183
+ def save_kv_layer(
184
+ self,
185
+ layer_name: str,
186
+ metadata: LMCacheMPConnectorMetadata,
187
+ **layer_io: Any,
188
+ ) -> None:
189
+ """Fan out ``save_kv_layer`` (connector ``save_kv_layer``; hot path)."""
190
+ for feature in self._features:
191
+ feature.save_kv_layer(layer_name, metadata, **layer_io)
192
+
193
+ def wait_for_save(self, event: _IpcEvent | None) -> None:
194
+ """Fan out ``wait_for_save`` (connector ``wait_for_save``)."""
195
+ for feature in self._features:
196
+ feature.wait_for_save(event)
197
+
198
+ def reclaim(self) -> None:
199
+ """Fan out ``reclaim`` (adapter ``get_finished``)."""
200
+ for feature in self._features:
201
+ feature.reclaim()
202
+
203
+ def reregister(self) -> bool:
204
+ """Fan out ``reregister`` (adapter heartbeat recovery)."""
205
+ for feature in self._features:
206
+ if not feature.reregister():
207
+ return False
208
+ return True
209
+
210
+ def shutdown(self) -> None:
211
+ """Fan out ``shutdown`` (adapter ``shutdown``)."""
212
+ for feature in self._features:
213
+ feature.shutdown()