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.
- lmcache/__init__.py +30 -10
- lmcache/_version.py +3 -3
- lmcache/cli/main.py +4 -0
- lmcache/integration/sglang/multi_process_adapter.py +95 -2
- lmcache/integration/sglang/sglang_adapter.py +21 -14
- lmcache/integration/sglang/utils.py +1 -1
- lmcache/integration/vllm/experimental/__init__.py +12 -0
- lmcache/integration/vllm/experimental/dispatcher.py +213 -0
- lmcache/integration/vllm/kv_cache_group_edits.py +157 -7
- lmcache/integration/vllm/lmcache_mp_connector.py +91 -16
- lmcache/integration/vllm/lmcache_mp_connector_0201.py +5 -5
- lmcache/integration/vllm/utils.py +24 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +67 -78
- lmcache/lmcache_frontend/app.py +14 -2
- lmcache/lmcache_frontend/heartbeat.py +27 -3
- lmcache/lmcache_frontend/run_mp_server_with_frontend.sh +31 -6
- lmcache/sdk/__init__.py +38 -5
- lmcache/sdk/batch.py +57 -45
- lmcache/sdk/cache_kind.py +50 -0
- lmcache/sdk/context.py +533 -0
- lmcache/sdk/kvcache.py +10 -507
- lmcache/sdk/qcache.py +40 -0
- lmcache/sdk/qringbuffer.py +765 -0
- lmcache/sdk/{stream.py → request.py} +84 -71
- lmcache/sdk/wrapper/contiguous.py +11 -5
- lmcache/tools/simple_discover_service.py +22 -1
- lmcache/v1/distributed/api.py +51 -0
- lmcache/v1/distributed/internal_api.py +14 -1
- lmcache/v1/distributed/l1_manager.py +29 -7
- lmcache/v1/distributed/l2_adapters/base.py +43 -0
- lmcache/v1/distributed/l2_adapters/dax_l2_adapter.py +45 -4
- lmcache/v1/distributed/l2_adapters/fault_inject_l2_adapter.py +5 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +79 -9
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +21 -11
- lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py +7 -5
- lmcache/v1/distributed/l2_adapters/sagemaker_hyperpod_client.py +92 -12
- lmcache/v1/distributed/l2_adapters/serde_wrapper.py +11 -2
- lmcache/v1/distributed/memory_manager/devdax_l1_memory_manager.py +88 -1
- lmcache/v1/distributed/memory_manager/gds_l1_memory_manager.py +12 -1
- lmcache/v1/distributed/memory_manager/l1_manager_protocol.py +5 -1
- lmcache/v1/distributed/memory_manager/l1_memory_manager.py +12 -1
- lmcache/v1/distributed/serde/__init__.py +12 -0
- lmcache/v1/distributed/serde/aesgcm.py +180 -0
- lmcache/v1/distributed/serde/asym_k16_v8.py +9 -4
- lmcache/v1/distributed/serde/async_processor.py +10 -5
- lmcache/v1/distributed/serde/base.py +13 -3
- lmcache/v1/distributed/serde/fp8.py +5 -5
- lmcache/v1/distributed/serde/key_provider.py +66 -0
- lmcache/v1/distributed/serde/multi.py +11 -7
- lmcache/v1/distributed/serde/turboquant/turboquant.py +11 -7
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +21 -71
- lmcache/v1/distributed/storage_manager.py +23 -46
- lmcache/v1/gpu_connector/gpu_connectors.py +23 -17
- lmcache/v1/gpu_connector/hpu_connector.py +6 -3
- lmcache/v1/gpu_connector/kv_format/__init__.py +5 -1
- lmcache/v1/gpu_connector/kv_format/contiguity.py +24 -3
- lmcache/v1/gpu_connector/kv_format/detection.py +25 -0
- lmcache/v1/gpu_connector/kv_format/detectors/vllm.py +9 -13
- lmcache/v1/gpu_connector/kv_format/specs/base.py +10 -0
- lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bs_nh_cs.py +68 -0
- lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bs_nh_two_hs.py +14 -3
- lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_bsv_bss.py +23 -0
- lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_nh_bs_cs.py +67 -0
- lmcache/v1/gpu_connector/kv_format/specs/nl_x_nb_nh_bs_two_hs.py +14 -3
- lmcache/v1/gpu_connector/musa_connectors.py +7 -4
- lmcache/v1/gpu_connector/utils.py +14 -7
- lmcache/v1/gpu_connector/xpu_connectors.py +12 -9
- lmcache/v1/kv_layer_groups.py +1 -1
- lmcache/v1/memory_allocators/devdax_memory_allocator.py +496 -157
- lmcache/v1/metadata.py +6 -6
- lmcache/v1/mp_coordinator/api.py +116 -0
- lmcache/v1/mp_coordinator/app.py +5 -2
- lmcache/v1/mp_coordinator/blend_client.py +19 -18
- lmcache/v1/mp_coordinator/cache_control/eviction_manager.py +8 -4
- lmcache/v1/mp_coordinator/cache_control/prefetch_manager.py +8 -5
- lmcache/v1/mp_coordinator/cache_control/resync_manager.py +9 -3
- lmcache/v1/mp_coordinator/cache_events.py +324 -0
- lmcache/v1/mp_coordinator/http_apis/cache_api.py +1 -1
- lmcache/v1/mp_coordinator/http_apis/dependencies.py +4 -0
- lmcache/v1/mp_coordinator/http_apis/directory_api.py +137 -0
- lmcache/v1/mp_coordinator/http_apis/quota_api.py +1 -1
- lmcache/v1/mp_coordinator/key_directory.py +328 -0
- lmcache/v1/mp_coordinator/schemas.py +119 -1
- lmcache/v1/mp_observability/README.md +4 -0
- lmcache/v1/mp_observability/config.py +41 -0
- lmcache/v1/mp_observability/event.py +6 -0
- lmcache/v1/mp_observability/gc_monitor.py +186 -0
- lmcache/v1/mp_observability/trace/codecs.py +33 -0
- lmcache/v1/multiprocess/cache_control/object_service.py +1 -2
- lmcache/v1/multiprocess/cache_control/prefetch_service.py +1 -1
- lmcache/v1/multiprocess/config.py +45 -29
- lmcache/v1/multiprocess/custom_types.py +1 -1
- lmcache/v1/multiprocess/futures.py +55 -35
- lmcache/v1/multiprocess/http_apis/cache_api.py +2 -1
- lmcache/v1/multiprocess/http_apis/schemas.py +1 -2
- lmcache/v1/multiprocess/http_server.py +38 -7
- lmcache/v1/multiprocess/modules/blend.py +2 -2
- lmcache/v1/multiprocess/modules/blend_v3.py +823 -107
- lmcache/v1/multiprocess/modules/experimental/__init__.py +10 -0
- lmcache/v1/multiprocess/modules/experimental/qstore.py +551 -0
- lmcache/v1/multiprocess/modules/lmcache_driven_transfer.py +56 -39
- lmcache/v1/multiprocess/modules/lookup.py +7 -4
- lmcache/v1/multiprocess/modules/management.py +19 -0
- lmcache/v1/multiprocess/modules/p2p_controller.py +18 -13
- lmcache/v1/multiprocess/native_completion.py +32 -29
- lmcache/v1/multiprocess/protocols/README.md +69 -24
- lmcache/v1/multiprocess/protocols/base.py +6 -0
- lmcache/v1/multiprocess/protocols/blend_v3.py +14 -3
- lmcache/v1/multiprocess/protocols/controller.py +11 -0
- lmcache/v1/multiprocess/protocols/engine.py +37 -0
- lmcache/v1/multiprocess/protocols/p2p.py +3 -2
- lmcache/v1/multiprocess/server.py +46 -3
- lmcache/v1/multiprocess/transfer_context/worker_transfer.py +259 -23
- lmcache/v1/multiprocess/warm_prefetch.py +7 -4
- lmcache/v1/periodic_thread.py +24 -2
- lmcache/v1/platform/__init__.py +68 -93
- lmcache/v1/platform/_device_detect.py +167 -0
- lmcache/v1/platform/base/__init__.py +9 -0
- lmcache/v1/platform/{base_cache_context.py → base/cache_context.py} +4 -4
- lmcache/v1/platform/base/device_ops.py +242 -0
- lmcache/v1/platform/{base_device_spec.py → base/device_spec.py} +72 -7
- lmcache/v1/platform/base/event_ipc.py +274 -0
- lmcache/v1/platform/{base_ipc_wrapper.py → base/ipc_wrapper.py} +2 -2
- lmcache/v1/platform/cache_context.py +25 -115
- lmcache/v1/platform/cpu/__init__.py +46 -14
- lmcache/v1/platform/cpu/cache_context.py +11 -16
- lmcache/v1/platform/cpu/device_ops.py +19 -0
- lmcache/v1/platform/cpu/shm.py +9 -8
- lmcache/v1/platform/cuda/__init__.py +38 -6
- lmcache/v1/platform/cuda/cache_context.py +2 -2
- lmcache/v1/platform/cuda/device_ops.py +39 -0
- lmcache/v1/platform/cuda/ipc_wrapper.py +1 -1
- lmcache/v1/platform/cuda/pin_memory.py +1 -1
- lmcache/v1/platform/hpu/__init__.py +8 -3
- lmcache/v1/platform/hpu/device_ops.py +19 -0
- lmcache/v1/platform/kv_wrap.py +97 -0
- lmcache/v1/platform/musa/__init__.py +23 -4
- lmcache/v1/platform/musa/device_ops.py +85 -0
- lmcache/v1/platform/musa/event_ipc.py +121 -0
- lmcache/v1/platform/musa/ipc_wrapper.py +203 -153
- lmcache/v1/platform/musa/native_kv_transfer.py +7 -7
- lmcache/v1/platform/ops_types.py +208 -0
- lmcache/{python_ops_fallback.py → v1/platform/torch_ops.py} +45 -204
- lmcache/v1/platform/xpu/__init__.py +8 -3
- lmcache/v1/platform/xpu/device_ops.py +39 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +16 -8
- lmcache/v1/storage_backend/local_cpu_backend.py +2 -2
- lmcache/v1/storage_backend/pd_backend_async.py +16 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +14 -8
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +9 -4
- lmcache/v1/storage_backend/raw_block/core.py +15 -31
- {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/METADATA +4 -1
- {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/RECORD +158 -130
- lmcache/v1/distributed/tiers.py +0 -23
- lmcache/v1/platform/musa/ops.py +0 -91
- /lmcache/v1/platform/{base_pin_memory.py → base/pin_memory.py} +0 -0
- {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/WHEEL +0 -0
- {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/entry_points.txt +0 -0
- {lmcache_cli-0.5.3.dev0.dist-info → lmcache_cli-0.5.3rc2.dev0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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.
|
|
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 = '
|
|
24
|
+
__commit_id__ = commit_id = 'g0427938a4'
|
lmcache/cli/main.py
CHANGED
|
@@ -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
|
-
).
|
|
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
|
-
.
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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(
|
|
372
|
+
logger.info("LMCache store_kv completed: stored %d tokens", len(token_ids))
|
|
369
373
|
except Exception as e:
|
|
370
374
|
logger.error(
|
|
371
|
-
|
|
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(
|
|
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(
|
|
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()
|