lmcache-kvblockd 0.2.0__tar.gz

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 (28) hide show
  1. lmcache_kvblockd-0.2.0/.gitignore +56 -0
  2. lmcache_kvblockd-0.2.0/PKG-INFO +88 -0
  3. lmcache_kvblockd-0.2.0/README.md +70 -0
  4. lmcache_kvblockd-0.2.0/pyproject.toml +32 -0
  5. lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/__init__.py +1 -0
  6. lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/adapter.py +127 -0
  7. lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/connector.py +539 -0
  8. lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/meta.py +77 -0
  9. lmcache_kvblockd-0.2.0/tests/test_connector_unit.py +251 -0
  10. lmcache_kvblockd-0.2.0/tests/test_fleet_adapter_urls.py +328 -0
  11. lmcache_kvblockd-0.2.0/tests/test_fleet_allocator_serialize.py +398 -0
  12. lmcache_kvblockd-0.2.0/tests/test_fleet_connector_fakeclient.py +891 -0
  13. lmcache_kvblockd-0.2.0/tests/test_fleet_meta_boundary.py +154 -0
  14. lmcache_kvblockd-0.2.0/tests/test_health_monitor_contract.py +55 -0
  15. lmcache_kvblockd-0.2.0/tests/test_initialization_deadline.py +102 -0
  16. lmcache_kvblockd-0.2.0/tests/test_initialization_ownership.py +142 -0
  17. lmcache_kvblockd-0.2.0/tests/test_invalid_metadata_cleanup.py +79 -0
  18. lmcache_kvblockd-0.2.0/tests/test_live_metadata_recovery.py +150 -0
  19. lmcache_kvblockd-0.2.0/tests/test_lmcache_registration.py +142 -0
  20. lmcache_kvblockd-0.2.0/tests/test_meta.py +51 -0
  21. lmcache_kvblockd-0.2.0/tests/test_operation_deadline.py +244 -0
  22. lmcache_kvblockd-0.2.0/tests/test_pin_lease_contract.py +40 -0
  23. lmcache_kvblockd-0.2.0/tests/test_real_memory_contract.py +403 -0
  24. lmcache_kvblockd-0.2.0/tests/test_real_retrieval_contract.py +152 -0
  25. lmcache_kvblockd-0.2.0/tests/test_receive_ownership.py +273 -0
  26. lmcache_kvblockd-0.2.0/tests/test_sync_lookup_contract.py +40 -0
  27. lmcache_kvblockd-0.2.0/tests/test_tag_identity_contract.py +56 -0
  28. lmcache_kvblockd-0.2.0/tests/test_unsafe_copy_path.py +54 -0
@@ -0,0 +1,56 @@
1
+ # ── PRIVATE RESEARCH VAULT — never commit ─────────────────────────
2
+ # All competitive research, strategy, business plans, drafts, and course material.
3
+ research/
4
+
5
+ # OS noise
6
+ .DS_Store
7
+
8
+ # Build artifacts
9
+ bin/
10
+ dist/
11
+ *.prof
12
+ *.test
13
+ testdata/corpus-large/
14
+
15
+ # External build dependencies fetched at build time (never vendored):
16
+ # ai-dynamo/nixl checkout for the adapters/nixl plugin (see its README).
17
+ build-deps/
18
+ adapters/nixl/build/
19
+
20
+ # Local env
21
+ .env
22
+
23
+ # Soak ARTIFACTS (logs, JSONL, pprof snapshots) — never the driver source
24
+ /soak
25
+ /soak.jsonl
26
+ bench/rigs/aws-soak/results/
27
+ bench/rigs/aws-dramgate/allocs-*.pb.gz
28
+
29
+ # Claude session rules — private, never push
30
+ CLAUDE.md
31
+
32
+ # Compiled command binaries accidentally built into the tree
33
+ /xferspike
34
+ /kvblockd
35
+ /kvbctl
36
+ cmd/xferspike/xferspike
37
+
38
+ # Python
39
+ __pycache__/
40
+ *.pyc
41
+ .pytest_cache/
42
+ .ruff_cache/
43
+ *.egg-info/
44
+ python/**/dist/
45
+
46
+ # mutation-testing artifacts
47
+ report.json
48
+ bench/rigs/*/.rig-state
49
+
50
+ # locally built bench binaries
51
+ /kvbench
52
+ /crash
53
+ /getbench
54
+ internal/store/modeltest/testdata/rapid/
55
+ bench/rigs/aws-s3soak/results/
56
+ .claude/
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.5
2
+ Name: lmcache-kvblockd
3
+ Version: 0.2.0
4
+ Summary: LMCache remote-storage backend backed by kvblockd
5
+ Project-URL: Homepage, https://kvstash.com
6
+ Project-URL: Documentation, https://kvstash.com/integrations/lmcache/
7
+ Project-URL: Get the daemon, https://kvstash.com/docs/install/
8
+ Author-email: Rudray Mehra <rudraymehra@gmail.com>
9
+ License-Expression: Apache-2.0
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: kvblockd>=0.2.0
12
+ Requires-Dist: lmcache<0.6,>=0.5.1
13
+ Requires-Dist: numpy
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=8; extra == 'test'
16
+ Requires-Dist: torch; extra == 'test'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # lmcache-kvblockd
20
+
21
+ [LMCache](https://github.com/LMCache/LMCache) remote storage plugin for
22
+ [KVStash](https://kvstash.com). vLLM deployments that already run LMCache can
23
+ keep KV in the KVStash daemon (`kvblockd://` scheme) and reload it after a
24
+ restart or from another server.
25
+
26
+ - Get the daemon (free download): https://kvstash.com/docs/install/
27
+ - Full guide: https://kvstash.com/integrations/lmcache/
28
+
29
+ Linux x86-64 only (LMCache ships manylinux wheels only).
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ pip install lmcache-kvblockd
35
+ ```
36
+
37
+ ## Configure
38
+
39
+ ```sh
40
+ PYTHONHASHSEED=0 vllm serve MODEL --kv-transfer-config '{
41
+ "kv_connector": "LMCacheConnectorV1", "kv_role": "kv_both",
42
+ "kv_connector_extra_config": {
43
+ "lmcache.remote_storage_plugins": "kvblockd",
44
+ "lmcache.local_cpu": true,
45
+ "lmcache.chunk_size": 256,
46
+ "lmcache.extra_config": {
47
+ "kvblockd_token": "YOUR_TOKEN",
48
+ "remote_storage_plugin.kvblockd.module_path": "lmcache_kvblockd.adapter",
49
+ "remote_storage_plugin.kvblockd.class_name": "KvblockdConnectorAdapter",
50
+ "remote_storage_plugin.kvblockd.url": "kvblockd://STORE_HOST:9440?namespace=lmcache&streams=4"
51
+ }
52
+ }
53
+ }'
54
+ ```
55
+
56
+ - The address goes in `remote_storage_plugin.kvblockd.url`. LMCache ignores
57
+ `remote_url` when `remote_storage_plugins` is set, and setting both creates
58
+ a second backend that doubles every write.
59
+ - Set `PYTHONHASHSEED=0` on every worker. LMCache's chunk keys depend on it,
60
+ so workers with different seeds cannot share cache.
61
+ - Use a separate namespace per model revision, dtype and cache layout.
62
+
63
+ ## LMCache's CUDA extension is required on vLLM 0.33 and newer
64
+
65
+ Without its compiled `lmcache.cuda_ops` extension, LMCache copies KV between
66
+ GPU and CPU with a torch fallback that does not handle the fused K/V layout of
67
+ vLLM 0.33. The bytes it hands the plugin are then not the KV, and a reload
68
+ gives wrong answers. When CUDA is present, the extension does not import and
69
+ vLLM is 0.33 or newer, the plugin refuses to start and says why; LMCache keeps
70
+ serving without the remote tier. Check before you deploy:
71
+
72
+ ```sh
73
+ python -c "import lmcache.cuda_ops"
74
+ ```
75
+
76
+ If that fails, install an LMCache build compiled for your torch version.
77
+
78
+ ## Behaviour notes
79
+
80
+ - Cache tags must have string names and values without `@` or `%` (LMCache's
81
+ text keys do not escape them). Unsupported tags become cache misses.
82
+ - GET and lookup deadlines include queueing and connection set-up.
83
+ - Lookup with `pin=True` reports only the leading run of keys whose
84
+ time-limited leases were acknowledged. A lease can expire; LMCache's full
85
+ asynchronous pin/unpin lifecycle is not implemented.
86
+ - Records with malformed headers or contradictory tensor sizes are removed so
87
+ they can be written again. Other read problems stay misses without deleting
88
+ valid records. Do not share a namespace between incompatible writers.
@@ -0,0 +1,70 @@
1
+ # lmcache-kvblockd
2
+
3
+ [LMCache](https://github.com/LMCache/LMCache) remote storage plugin for
4
+ [KVStash](https://kvstash.com). vLLM deployments that already run LMCache can
5
+ keep KV in the KVStash daemon (`kvblockd://` scheme) and reload it after a
6
+ restart or from another server.
7
+
8
+ - Get the daemon (free download): https://kvstash.com/docs/install/
9
+ - Full guide: https://kvstash.com/integrations/lmcache/
10
+
11
+ Linux x86-64 only (LMCache ships manylinux wheels only).
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ pip install lmcache-kvblockd
17
+ ```
18
+
19
+ ## Configure
20
+
21
+ ```sh
22
+ PYTHONHASHSEED=0 vllm serve MODEL --kv-transfer-config '{
23
+ "kv_connector": "LMCacheConnectorV1", "kv_role": "kv_both",
24
+ "kv_connector_extra_config": {
25
+ "lmcache.remote_storage_plugins": "kvblockd",
26
+ "lmcache.local_cpu": true,
27
+ "lmcache.chunk_size": 256,
28
+ "lmcache.extra_config": {
29
+ "kvblockd_token": "YOUR_TOKEN",
30
+ "remote_storage_plugin.kvblockd.module_path": "lmcache_kvblockd.adapter",
31
+ "remote_storage_plugin.kvblockd.class_name": "KvblockdConnectorAdapter",
32
+ "remote_storage_plugin.kvblockd.url": "kvblockd://STORE_HOST:9440?namespace=lmcache&streams=4"
33
+ }
34
+ }
35
+ }'
36
+ ```
37
+
38
+ - The address goes in `remote_storage_plugin.kvblockd.url`. LMCache ignores
39
+ `remote_url` when `remote_storage_plugins` is set, and setting both creates
40
+ a second backend that doubles every write.
41
+ - Set `PYTHONHASHSEED=0` on every worker. LMCache's chunk keys depend on it,
42
+ so workers with different seeds cannot share cache.
43
+ - Use a separate namespace per model revision, dtype and cache layout.
44
+
45
+ ## LMCache's CUDA extension is required on vLLM 0.33 and newer
46
+
47
+ Without its compiled `lmcache.cuda_ops` extension, LMCache copies KV between
48
+ GPU and CPU with a torch fallback that does not handle the fused K/V layout of
49
+ vLLM 0.33. The bytes it hands the plugin are then not the KV, and a reload
50
+ gives wrong answers. When CUDA is present, the extension does not import and
51
+ vLLM is 0.33 or newer, the plugin refuses to start and says why; LMCache keeps
52
+ serving without the remote tier. Check before you deploy:
53
+
54
+ ```sh
55
+ python -c "import lmcache.cuda_ops"
56
+ ```
57
+
58
+ If that fails, install an LMCache build compiled for your torch version.
59
+
60
+ ## Behaviour notes
61
+
62
+ - Cache tags must have string names and values without `@` or `%` (LMCache's
63
+ text keys do not escape them). Unsupported tags become cache misses.
64
+ - GET and lookup deadlines include queueing and connection set-up.
65
+ - Lookup with `pin=True` reports only the leading run of keys whose
66
+ time-limited leases were acknowledged. A lease can expire; LMCache's full
67
+ asynchronous pin/unpin lifecycle is not implemented.
68
+ - Records with malformed headers or contradictory tensor sizes are removed so
69
+ they can be written again. Other read problems stay misses without deleting
70
+ valid records. Do not share a namespace between incompatible writers.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lmcache-kvblockd"
7
+ version = "0.2.0"
8
+ description = "LMCache remote-storage backend backed by kvblockd"
9
+ requires-python = ">=3.10"
10
+ readme = "README.md"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Rudray Mehra", email = "rudraymehra@gmail.com" }]
13
+ dependencies = [
14
+ "kvblockd>=0.2.0",
15
+ "lmcache>=0.5.1,<0.6",
16
+ "numpy", # zero-copy MemoryObj views via tensor.numpy() (torch does not pull it)
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://kvstash.com"
21
+ Documentation = "https://kvstash.com/integrations/lmcache/"
22
+ "Get the daemon" = "https://kvstash.com/docs/install/"
23
+
24
+ [project.optional-dependencies]
25
+ test = ["pytest>=8", "torch"]
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/lmcache_kvblockd"]
29
+
30
+ [tool.ruff]
31
+ line-length = 100
32
+ target-version = "py310"
@@ -0,0 +1 @@
1
+ """LMCache remote-storage backend backed by kvblockd."""
@@ -0,0 +1,127 @@
1
+ """KvblockdConnectorAdapter — registers the kvblockd:// scheme with LMCache's
2
+ ConnectorManager (plugin path: remote_storage_plugins + extra_config
3
+ module_path/class_name; see docs/notes/lmcache-head-verify.md).
4
+
5
+ TWO URL shapes must parse, because LMCache 0.5.x dials remote backends two
6
+ different ways (lmcache/v1/storage_backend/remote_backend.py, init_connection):
7
+
8
+ 1. legacy `remote_url` backend → url = "kvblockd://host:port?..."
9
+ 2. plugin backend (the `remote_storage_plugins` path) → url =
10
+ "plugin://kvblockd" — a VIRTUAL url carrying no endpoint at all.
11
+
12
+ An adapter that only matches kvblockd:// makes the plugin backend fail
13
+ "No adapter found for URL: plugin://kvblockd"; RemoteBackend swallows that
14
+ (connection stays None) and every op silently degrades to a miss — the
15
+ GPU-box zero-bytes failure. For plugin:// the real endpoint comes from
16
+ extra_config["remote_storage_plugin.kvblockd.url"] (falling back to
17
+ config.remote_url when it is a kvblockd:// url). This mirrors how builtin
18
+ adapters (fs_adapter.py) treat plugin:// URLs in lmcache 0.5.1.
19
+
20
+ The LMCache ConnectorAdapter base is resolved at import time; if LMCache is
21
+ absent (CI import checks, or unit tests without lmcache) we fall back to
22
+ `object` so the module still imports and the class still instantiates."""
23
+
24
+ from __future__ import annotations
25
+
26
+ from .connector import make_connector
27
+
28
+ try:
29
+ from lmcache.v1.storage_backend.connector import ConnectorAdapter as _Base
30
+ except Exception: # noqa: BLE001 — availability fallback: LMCache not installed — keep the module importable
31
+ _Base = object
32
+
33
+ SCHEME = "kvblockd://"
34
+ PLUGIN_SCHEME = "plugin://"
35
+ PLUGIN_TYPE = "kvblockd"
36
+
37
+
38
+ def _plugin_type(name: str) -> str:
39
+ """Mirror lmcache's extract_plugin_type: '{type}' or '{type}.{instance}'."""
40
+ return name.split(".", 1)[0]
41
+
42
+
43
+ def unsafe_copy_path() -> str | None:
44
+ """Why storing through this LMCache would write wrong KV, or None.
45
+
46
+ Without its compiled lmcache.cuda_ops extension LMCache copies between GPU
47
+ and CPU with a torch fallback that has no branch for the fused K/V layout
48
+ vLLM 0.33 allocates (LBNHC), so the bytes it hands us are not the KV. The
49
+ daemon never overwrites a stored key, so such blocks would stay wrong
50
+ after LMCache is fixed: refuse instead.
51
+ """
52
+ try:
53
+ import torch
54
+
55
+ if not torch.cuda.is_available():
56
+ return None # CPU-only processes (tests, CI) never take the GPU copy path
57
+ except Exception: # noqa: BLE001 - no torch, no GPU copy path
58
+ return None
59
+ try:
60
+ import lmcache.cuda_ops # noqa: F401
61
+ return None
62
+ except ImportError:
63
+ pass
64
+ try:
65
+ from importlib.metadata import version
66
+
67
+ major_minor = tuple(int(x) for x in version("vllm").split(".")[:2])
68
+ except Exception: # noqa: BLE001 - unknown vLLM: keep the old behaviour
69
+ return None
70
+ if major_minor >= (0, 33):
71
+ return ("kvblockd plugin disabled: LMCache's compiled lmcache.cuda_ops extension is not "
72
+ "importable, and its torch fallback copies vLLM >= 0.33's fused K/V layout into the "
73
+ "wrong blocks. Install an LMCache build with its CUDA extension for this torch.")
74
+ return None
75
+
76
+
77
+ class KvblockdConnectorAdapter(_Base):
78
+ """Matches kvblockd:// and plugin://kvblockd[.instance] URLs and builds a
79
+ KvblockdRemoteConnector."""
80
+
81
+ def __init__(self, schema: str = SCHEME):
82
+ try:
83
+ super().__init__(schema) # ConnectorAdapter(schema) when present
84
+ except TypeError:
85
+ self.schema = schema # object() base: set it ourselves
86
+
87
+ def can_parse(self, url: str) -> bool:
88
+ if url.startswith(SCHEME):
89
+ return True
90
+ if url.startswith(PLUGIN_SCHEME):
91
+ return _plugin_type(url[len(PLUGIN_SCHEME):]) == PLUGIN_TYPE
92
+ return False
93
+
94
+ def create_connector(self, context):
95
+ reason = unsafe_copy_path()
96
+ if reason:
97
+ # RemoteBackend.init_connection catches this and runs without the
98
+ # remote tier: nothing reaches the store, so nothing wrong persists.
99
+ raise RuntimeError(reason)
100
+ return make_connector(context, url=self._resolve_url(context))
101
+
102
+ @staticmethod
103
+ def _resolve_url(context) -> str:
104
+ """The real kvblockd:// endpoint for this context.
105
+
106
+ plugin:// URLs are virtual — the endpoint lives in config. Raising here
107
+ is deliberate and SAFE: RemoteBackend.init_connection catches it and
108
+ logs a warning, and a loud misconfiguration beats a silent no-op."""
109
+ url = getattr(context, "url", "") or ""
110
+ if url.startswith(SCHEME):
111
+ return url
112
+ cfg = getattr(context, "config", None)
113
+ extra = getattr(cfg, "extra_config", None) or {}
114
+ pname = getattr(context, "plugin_name", None) or url[len(PLUGIN_SCHEME):] or PLUGIN_TYPE
115
+ # Full instance name first ("kvblockd.primary"), then the bare type.
116
+ for name in dict.fromkeys((pname, _plugin_type(pname))):
117
+ candidate = extra.get(f"remote_storage_plugin.{name}.url")
118
+ if candidate:
119
+ return candidate
120
+ remote_url = getattr(cfg, "remote_url", None)
121
+ if remote_url and remote_url.startswith(SCHEME):
122
+ return remote_url
123
+ raise ValueError(
124
+ f"kvblockd plugin selected (url={url!r}) but no endpoint configured: set "
125
+ f"extra_config['remote_storage_plugin.{_plugin_type(pname)}.url'] = "
126
+ f"'kvblockd://HOST:PORT?namespace=...&streams=...'"
127
+ )