lmcache-cli 0.4.5.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 (399) hide show
  1. lmcache/__init__.py +84 -0
  2. lmcache/_version.py +24 -0
  3. lmcache/cli/__init__.py +1 -0
  4. lmcache/cli/commands/__init__.py +34 -0
  5. lmcache/cli/commands/base.py +157 -0
  6. lmcache/cli/commands/bench/__init__.py +557 -0
  7. lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
  8. lmcache/cli/commands/bench/engine_bench/config.py +245 -0
  9. lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
  10. lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
  11. lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
  12. lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
  13. lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
  14. lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
  15. lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
  16. lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
  17. lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
  18. lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
  19. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
  20. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
  21. lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
  22. lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
  23. lmcache/cli/commands/describe.py +310 -0
  24. lmcache/cli/commands/kvcache.py +133 -0
  25. lmcache/cli/commands/mock.py +75 -0
  26. lmcache/cli/commands/ping.py +113 -0
  27. lmcache/cli/commands/query/__init__.py +155 -0
  28. lmcache/cli/commands/query/prompt.py +134 -0
  29. lmcache/cli/commands/query/request.py +357 -0
  30. lmcache/cli/commands/server.py +99 -0
  31. lmcache/cli/commands/tool/__init__.py +63 -0
  32. lmcache/cli/commands/tool/cache_simulator.py +113 -0
  33. lmcache/cli/commands/trace/__init__.py +505 -0
  34. lmcache/cli/commands/trace/dispatch.py +249 -0
  35. lmcache/cli/commands/trace/driver.py +372 -0
  36. lmcache/cli/commands/trace/stats.py +289 -0
  37. lmcache/cli/documents/lmcache.txt +11 -0
  38. lmcache/cli/main.py +42 -0
  39. lmcache/cli/metrics/__init__.py +29 -0
  40. lmcache/cli/metrics/formatter.py +171 -0
  41. lmcache/cli/metrics/handler.py +94 -0
  42. lmcache/cli/metrics/metrics.py +161 -0
  43. lmcache/cli/metrics/section.py +77 -0
  44. lmcache/connections.py +173 -0
  45. lmcache/integration/__init__.py +2 -0
  46. lmcache/integration/base_service_factory.py +165 -0
  47. lmcache/integration/request_telemetry/__init__.py +1 -0
  48. lmcache/integration/request_telemetry/base.py +51 -0
  49. lmcache/integration/request_telemetry/factory.py +113 -0
  50. lmcache/integration/request_telemetry/fastapi.py +109 -0
  51. lmcache/integration/request_telemetry/noop.py +35 -0
  52. lmcache/integration/sglang/__init__.py +2 -0
  53. lmcache/integration/sglang/sglang_adapter.py +326 -0
  54. lmcache/integration/sglang/utils.py +39 -0
  55. lmcache/integration/vllm/__init__.py +1 -0
  56. lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
  57. lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
  58. lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
  59. lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
  60. lmcache/integration/vllm/utils.py +433 -0
  61. lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
  62. lmcache/integration/vllm/vllm_service_factory.py +339 -0
  63. lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
  64. lmcache/logging.py +107 -0
  65. lmcache/native_storage_ops.pyi +230 -0
  66. lmcache/non_cuda_equivalents.py +1424 -0
  67. lmcache/observability.py +1958 -0
  68. lmcache/storage_backend/serde/__init__.py +1 -0
  69. lmcache/storage_backend/serde/cachegen_basics.py +210 -0
  70. lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
  71. lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
  72. lmcache/storage_backend/serde/serde.py +75 -0
  73. lmcache/tools/__init__.py +1 -0
  74. lmcache/tools/cache_simulator/README.md +392 -0
  75. lmcache/tools/cache_simulator/__init__.py +1 -0
  76. lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
  77. lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
  78. lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
  79. lmcache/tools/cache_simulator/lru_cache.py +124 -0
  80. lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
  81. lmcache/tools/cache_simulator/simulator.py +795 -0
  82. lmcache/tools/controller_benchmark/README.md +161 -0
  83. lmcache/tools/controller_benchmark/__init__.py +1 -0
  84. lmcache/tools/controller_benchmark/__main__.py +331 -0
  85. lmcache/tools/controller_benchmark/benchmark.py +660 -0
  86. lmcache/tools/controller_benchmark/config.py +44 -0
  87. lmcache/tools/controller_benchmark/constants.py +10 -0
  88. lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
  89. lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
  90. lmcache/tools/controller_benchmark/handlers/base.py +47 -0
  91. lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
  92. lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
  93. lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
  94. lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
  95. lmcache/tools/controller_benchmark/handlers/register.py +56 -0
  96. lmcache/tools/mp_status_viewer/__init__.py +1 -0
  97. lmcache/tools/mp_status_viewer/__main__.py +95 -0
  98. lmcache/usage_context.py +417 -0
  99. lmcache/utils.py +665 -0
  100. lmcache/v1/__init__.py +2 -0
  101. lmcache/v1/api_server/__init__.py +2 -0
  102. lmcache/v1/api_server/__main__.py +537 -0
  103. lmcache/v1/basic_check.py +112 -0
  104. lmcache/v1/cache_controller/__init__.py +9 -0
  105. lmcache/v1/cache_controller/commands/__init__.py +15 -0
  106. lmcache/v1/cache_controller/commands/base.py +35 -0
  107. lmcache/v1/cache_controller/commands/full_sync.py +49 -0
  108. lmcache/v1/cache_controller/config.py +176 -0
  109. lmcache/v1/cache_controller/controller_manager.py +535 -0
  110. lmcache/v1/cache_controller/controllers/__init__.py +11 -0
  111. lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
  112. lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
  113. lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
  114. lmcache/v1/cache_controller/executor.py +463 -0
  115. lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
  116. lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
  117. lmcache/v1/cache_controller/frontend/static/index.html +234 -0
  118. lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
  119. lmcache/v1/cache_controller/full_sync_sender.py +475 -0
  120. lmcache/v1/cache_controller/locks.py +149 -0
  121. lmcache/v1/cache_controller/message.py +828 -0
  122. lmcache/v1/cache_controller/observability.py +208 -0
  123. lmcache/v1/cache_controller/utils.py +679 -0
  124. lmcache/v1/cache_controller/worker.py +665 -0
  125. lmcache/v1/cache_engine.py +2058 -0
  126. lmcache/v1/cache_interface.py +19 -0
  127. lmcache/v1/check/__init__.py +74 -0
  128. lmcache/v1/check/check_mode_gen.py +86 -0
  129. lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
  130. lmcache/v1/check/check_mode_test_remote.py +155 -0
  131. lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
  132. lmcache/v1/check/utils.py +571 -0
  133. lmcache/v1/compute/__init__.py +2 -0
  134. lmcache/v1/compute/attention/__init__.py +0 -0
  135. lmcache/v1/compute/attention/abstract.py +39 -0
  136. lmcache/v1/compute/attention/flash_attn.py +129 -0
  137. lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
  138. lmcache/v1/compute/attention/metadata.py +85 -0
  139. lmcache/v1/compute/attention/utils.py +14 -0
  140. lmcache/v1/compute/blend/__init__.py +7 -0
  141. lmcache/v1/compute/blend/blender.py +168 -0
  142. lmcache/v1/compute/blend/metadata.py +34 -0
  143. lmcache/v1/compute/blend/utils.py +63 -0
  144. lmcache/v1/compute/models/__init__.py +0 -0
  145. lmcache/v1/compute/models/base.py +141 -0
  146. lmcache/v1/compute/models/llama.py +9 -0
  147. lmcache/v1/compute/models/qwen3.py +24 -0
  148. lmcache/v1/compute/models/utils.py +68 -0
  149. lmcache/v1/compute/positional_encoding.py +199 -0
  150. lmcache/v1/config.py +848 -0
  151. lmcache/v1/config_base.py +848 -0
  152. lmcache/v1/distributed/api.py +248 -0
  153. lmcache/v1/distributed/config.py +321 -0
  154. lmcache/v1/distributed/error.py +64 -0
  155. lmcache/v1/distributed/eviction.py +192 -0
  156. lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
  157. lmcache/v1/distributed/eviction_policy/factory.py +27 -0
  158. lmcache/v1/distributed/eviction_policy/lru.py +244 -0
  159. lmcache/v1/distributed/eviction_policy/noop.py +50 -0
  160. lmcache/v1/distributed/internal_api.py +170 -0
  161. lmcache/v1/distributed/l1_manager.py +835 -0
  162. lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
  163. lmcache/v1/distributed/l2_adapters/base.py +360 -0
  164. lmcache/v1/distributed/l2_adapters/config.py +385 -0
  165. lmcache/v1/distributed/l2_adapters/factory.py +205 -0
  166. lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
  167. lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
  168. lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
  169. lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
  170. lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
  171. lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
  172. lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
  173. lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
  174. lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
  175. lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
  176. lmcache/v1/distributed/memory_manager.py +179 -0
  177. lmcache/v1/distributed/storage_controller.py +39 -0
  178. lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
  179. lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
  180. lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
  181. lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
  182. lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
  183. lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
  184. lmcache/v1/distributed/storage_manager.py +532 -0
  185. lmcache/v1/event_manager.py +145 -0
  186. lmcache/v1/exceptions/__init__.py +16 -0
  187. lmcache/v1/gpu_connector/__init__.py +126 -0
  188. lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
  189. lmcache/v1/gpu_connector/gpu_ops.py +85 -0
  190. lmcache/v1/gpu_connector/hpu_connector.py +326 -0
  191. lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
  192. lmcache/v1/gpu_connector/utils.py +890 -0
  193. lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
  194. lmcache/v1/health_monitor/__init__.py +1 -0
  195. lmcache/v1/health_monitor/base.py +587 -0
  196. lmcache/v1/health_monitor/checks/__init__.py +1 -0
  197. lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
  198. lmcache/v1/health_monitor/constants.py +36 -0
  199. lmcache/v1/internal_api_server/__init__.py +0 -0
  200. lmcache/v1/internal_api_server/api_registry.py +59 -0
  201. lmcache/v1/internal_api_server/api_server.py +120 -0
  202. lmcache/v1/internal_api_server/common/__init__.py +1 -0
  203. lmcache/v1/internal_api_server/common/env_api.py +22 -0
  204. lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
  205. lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
  206. lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
  207. lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
  208. lmcache/v1/internal_api_server/common/thread_api.py +63 -0
  209. lmcache/v1/internal_api_server/controller/__init__.py +1 -0
  210. lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
  211. lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
  212. lmcache/v1/internal_api_server/utils.py +43 -0
  213. lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
  214. lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
  215. lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
  216. lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
  217. lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
  218. lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
  219. lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
  220. lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
  221. lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
  222. lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
  223. lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
  224. lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
  225. lmcache/v1/kv_layer_groups.py +267 -0
  226. lmcache/v1/lazy_memory_allocator.py +284 -0
  227. lmcache/v1/lookup_client/__init__.py +25 -0
  228. lmcache/v1/lookup_client/abstract_client.py +77 -0
  229. lmcache/v1/lookup_client/async_lookup_message.py +50 -0
  230. lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
  231. lmcache/v1/lookup_client/factory.py +251 -0
  232. lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
  233. lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
  234. lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
  235. lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
  236. lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
  237. lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
  238. lmcache/v1/lookup_client/record_strategies/base.py +327 -0
  239. lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
  240. lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
  241. lmcache/v1/manager.py +539 -0
  242. lmcache/v1/memory_management.py +2619 -0
  243. lmcache/v1/metadata.py +114 -0
  244. lmcache/v1/mp_observability/AGENTS.override.md +21 -0
  245. lmcache/v1/mp_observability/README.md +204 -0
  246. lmcache/v1/mp_observability/config.py +340 -0
  247. lmcache/v1/mp_observability/event.py +100 -0
  248. lmcache/v1/mp_observability/event_bus.py +313 -0
  249. lmcache/v1/mp_observability/otel_init.py +145 -0
  250. lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
  251. lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
  252. lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
  253. lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
  254. lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
  255. lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
  256. lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
  257. lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
  258. lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
  259. lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
  260. lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
  261. lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
  262. lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
  263. lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
  264. lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
  265. lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
  266. lmcache/v1/mp_observability/trace/__init__.py +50 -0
  267. lmcache/v1/mp_observability/trace/codecs.py +255 -0
  268. lmcache/v1/mp_observability/trace/decorator.py +147 -0
  269. lmcache/v1/mp_observability/trace/format.py +132 -0
  270. lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
  271. lmcache/v1/mp_observability/trace/reader.py +167 -0
  272. lmcache/v1/mp_observability/trace/recorder.py +300 -0
  273. lmcache/v1/multiprocess/__init__.py +0 -0
  274. lmcache/v1/multiprocess/affinity_pool.py +102 -0
  275. lmcache/v1/multiprocess/blend_server_v2.py +891 -0
  276. lmcache/v1/multiprocess/config.py +253 -0
  277. lmcache/v1/multiprocess/custom_types.py +281 -0
  278. lmcache/v1/multiprocess/futures.py +194 -0
  279. lmcache/v1/multiprocess/gpu_context.py +511 -0
  280. lmcache/v1/multiprocess/http_server.py +235 -0
  281. lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
  282. lmcache/v1/multiprocess/mq.py +732 -0
  283. lmcache/v1/multiprocess/protocol.py +86 -0
  284. lmcache/v1/multiprocess/protocols/README.md +213 -0
  285. lmcache/v1/multiprocess/protocols/__init__.py +127 -0
  286. lmcache/v1/multiprocess/protocols/base.py +89 -0
  287. lmcache/v1/multiprocess/protocols/blend.py +109 -0
  288. lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
  289. lmcache/v1/multiprocess/protocols/controller.py +53 -0
  290. lmcache/v1/multiprocess/protocols/debug.py +34 -0
  291. lmcache/v1/multiprocess/protocols/engine.py +146 -0
  292. lmcache/v1/multiprocess/protocols/observability.py +39 -0
  293. lmcache/v1/multiprocess/server.py +1134 -0
  294. lmcache/v1/multiprocess/session.py +190 -0
  295. lmcache/v1/multiprocess/token_hasher.py +441 -0
  296. lmcache/v1/offload_server/__init__.py +17 -0
  297. lmcache/v1/offload_server/abstract_server.py +37 -0
  298. lmcache/v1/offload_server/message.py +30 -0
  299. lmcache/v1/offload_server/zmq_server.py +122 -0
  300. lmcache/v1/periodic_thread.py +579 -0
  301. lmcache/v1/pin_monitor.py +246 -0
  302. lmcache/v1/plugin/__init__.py +0 -0
  303. lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
  304. lmcache/v1/protocol.py +317 -0
  305. lmcache/v1/rpc/__init__.py +17 -0
  306. lmcache/v1/rpc/transport.py +105 -0
  307. lmcache/v1/rpc/zmq_transport.py +213 -0
  308. lmcache/v1/rpc_utils.py +165 -0
  309. lmcache/v1/server/__init__.py +2 -0
  310. lmcache/v1/server/__main__.py +170 -0
  311. lmcache/v1/server/storage_backend/__init__.py +21 -0
  312. lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
  313. lmcache/v1/server/storage_backend/local_backend.py +75 -0
  314. lmcache/v1/server/utils.py +21 -0
  315. lmcache/v1/standalone/__init__.py +1 -0
  316. lmcache/v1/standalone/__main__.py +583 -0
  317. lmcache/v1/standalone/manager.py +80 -0
  318. lmcache/v1/standalone/standalone_service_factory.py +86 -0
  319. lmcache/v1/storage_backend/__init__.py +313 -0
  320. lmcache/v1/storage_backend/abstract_backend.py +445 -0
  321. lmcache/v1/storage_backend/audit_backend.py +233 -0
  322. lmcache/v1/storage_backend/batched_message_sender.py +222 -0
  323. lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
  324. lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
  325. lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
  326. lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
  327. lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
  328. lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
  329. lmcache/v1/storage_backend/connector/__init__.py +443 -0
  330. lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
  331. lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
  332. lmcache/v1/storage_backend/connector/base_connector.py +379 -0
  333. lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
  334. lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
  335. lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
  336. lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
  337. lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
  338. lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
  339. lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
  340. lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
  341. lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
  342. lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
  343. lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
  344. lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
  345. lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
  346. lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
  347. lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
  348. lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
  349. lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
  350. lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
  351. lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
  352. lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
  353. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
  354. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
  355. lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
  356. lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
  357. lmcache/v1/storage_backend/gds_backend.py +1199 -0
  358. lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
  359. lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
  360. lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
  361. lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
  362. lmcache/v1/storage_backend/local_disk_backend.py +656 -0
  363. lmcache/v1/storage_backend/maru_backend.py +734 -0
  364. lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
  365. lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
  366. lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
  367. lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
  368. lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
  369. lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
  370. lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
  371. lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
  372. lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
  373. lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
  374. lmcache/v1/storage_backend/p2p_backend.py +788 -0
  375. lmcache/v1/storage_backend/path_sharder.py +117 -0
  376. lmcache/v1/storage_backend/pd_backend.py +646 -0
  377. lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
  378. lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
  379. lmcache/v1/storage_backend/remote_backend.py +624 -0
  380. lmcache/v1/storage_backend/resp_client.py +227 -0
  381. lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
  382. lmcache/v1/storage_backend/storage_manager.py +1352 -0
  383. lmcache/v1/system_detection.py +110 -0
  384. lmcache/v1/token_database.py +551 -0
  385. lmcache/v1/transfer_channel/__init__.py +83 -0
  386. lmcache/v1/transfer_channel/abstract.py +285 -0
  387. lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
  388. lmcache/v1/transfer_channel/nixl_channel.py +639 -0
  389. lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
  390. lmcache/v1/transfer_channel/transfer_utils.py +63 -0
  391. lmcache/v1/utils/__init__.py +1 -0
  392. lmcache/v1/utils/bloom_filter.py +109 -0
  393. lmcache/v1/utils/cache_utils.py +125 -0
  394. lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
  395. lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
  396. lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
  397. lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
  398. lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
  399. lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1090 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ # Standard
4
+ from dataclasses import dataclass
5
+ from typing import Any, Optional
6
+ import os
7
+ import threading
8
+
9
+ # Third Party
10
+ import torch
11
+ import zmq
12
+
13
+ # First Party
14
+ from lmcache.integration.request_telemetry.factory import RequestTelemetryFactory
15
+ from lmcache.utils import _lmcache_nvtx_annotate, init_logger
16
+ from lmcache.v1.multiprocess.custom_types import (
17
+ BlockAllocationRecord,
18
+ CudaIPCWrapper,
19
+ IPCCacheEngineKey,
20
+ KVCache,
21
+ )
22
+ from lmcache.v1.multiprocess.mq import MessageQueueClient, MessagingFuture
23
+ from lmcache.v1.multiprocess.protocol import RequestType, get_response_class
24
+ from lmcache.v1.periodic_thread import PeriodicThread, ThreadLevel, ThreadRunSummary
25
+
26
+ logger = init_logger(__name__)
27
+
28
+ # Timeout (seconds) for blocking MQ requests: initial chunk-size query,
29
+ # KV cache registration/unregistration, and other synchronous operations.
30
+ DEFAULT_MQ_TIMEOUT: float = 300.0
31
+ # Interval (seconds) between periodic heartbeat pings to the server.
32
+ DEFAULT_HEARTBEAT_INTERVAL: float = 10.0
33
+
34
+
35
+ def wrap_kv_caches(kv_caches: dict[str, torch.Tensor]) -> KVCache:
36
+ logger.info("KV caches keys are %s", list(kv_caches.keys()))
37
+ return [CudaIPCWrapper(tensor) for tensor in kv_caches.values()]
38
+
39
+
40
+ def send_lmcache_request(
41
+ mq_client: MessageQueueClient,
42
+ request_type: RequestType,
43
+ payloads: list[Any],
44
+ ) -> MessagingFuture[Any]:
45
+ """
46
+ Helper function to send the request to the LMCache multiprocess server
47
+
48
+ Args:
49
+ mq_client: The LMCache multiprocess mode message queue client
50
+ request_type: The request type
51
+ payloads: The request payloads
52
+
53
+ Returns:
54
+ A messaging future for the request
55
+ """
56
+
57
+ future = mq_client.submit_request(
58
+ request_type, payloads, get_response_class(request_type)
59
+ )
60
+ return future
61
+
62
+
63
+ def get_lmcache_chunk_size(
64
+ mq_client: MessageQueueClient,
65
+ ) -> int:
66
+ """
67
+ Helper function to get the LMCache chunk size from the server
68
+
69
+ Args:
70
+ mq_client: The LMCache multiprocess mode message queue client
71
+
72
+ Returns:
73
+ An integer representing the LMCache chunk size
74
+ """
75
+ future = send_lmcache_request(mq_client, RequestType.GET_CHUNK_SIZE, [])
76
+ chunk_size = future.result(timeout=DEFAULT_MQ_TIMEOUT)
77
+ return chunk_size
78
+
79
+
80
+ def send_ping(
81
+ mq_client: MessageQueueClient,
82
+ timeout: float,
83
+ ) -> bool:
84
+ """Send a PING request and return the result.
85
+
86
+ Returns:
87
+ True if server is healthy, False on timeout or error.
88
+ """
89
+ try:
90
+ future = send_lmcache_request(mq_client, RequestType.PING, [])
91
+ return future.result(timeout=timeout)
92
+ except TimeoutError:
93
+ return False
94
+ except Exception:
95
+ logger.debug("Ping failed with exception", exc_info=True)
96
+ return False
97
+
98
+
99
+ @dataclass
100
+ class ParallelStrategy:
101
+ use_mla: bool
102
+ """Whether to use the MLA."""
103
+
104
+ kv_world_size: int
105
+ """
106
+ The kv world size, kv_world_size may not be equal to the actual_world_size,
107
+ in the case of mla, it will 'exclude' the effect of TP, the value is
108
+ calculated by `extract_world_size_and_kv_rank` in `lmcache_mp_connector.py`.
109
+ """
110
+
111
+ kv_worker_id: int
112
+ """
113
+ The kv worker id of the sub-process, kv_worker_id may not be equal to the
114
+ actual_worker_id, in the case of mla, it will 'exclude' the effect of TP,
115
+ the value is calculated by `extract_world_size_and_kv_rank` in
116
+ `lmcache_mp_connector.py`.
117
+ """
118
+
119
+ actual_world_size: int
120
+ """The actual world size."""
121
+
122
+ actual_worker_id: int
123
+ """The actual worker id of the sub-process."""
124
+
125
+ tp_size: int
126
+ """The tensor parallel size."""
127
+
128
+ pp_size: int
129
+ """The pipeline parallel size."""
130
+
131
+
132
+ class HeartbeatThread(PeriodicThread):
133
+ """Periodically checks server health via PING.
134
+
135
+ Manages a threading.Event that adapters use to gate operations.
136
+ When unhealthy, the adapter enters degraded mode; if the server
137
+ recovers, the adapter automatically resumes normal operation.
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ mq_client: MessageQueueClient,
143
+ health_event: threading.Event,
144
+ interval: float = DEFAULT_HEARTBEAT_INTERVAL,
145
+ ):
146
+ """
147
+ Args:
148
+ mq_client: The message queue client used to send PING requests.
149
+ health_event: A threading.Event shared with the adapter.
150
+ Set when the server is healthy, cleared when unhealthy.
151
+ Adapters check this event to decide whether to proceed
152
+ with operations or enter degraded mode.
153
+ interval: Seconds between heartbeat pings and ping timeout.
154
+ """
155
+ super().__init__(
156
+ name="lmcache-heartbeat",
157
+ interval=interval,
158
+ level=ThreadLevel.CRITICAL,
159
+ )
160
+ self._mq_client = mq_client
161
+ self._health_event = health_event
162
+ self._interval = interval
163
+
164
+ def _execute(self) -> ThreadRunSummary:
165
+ was_healthy = self._health_event.is_set()
166
+ healthy = send_ping(self._mq_client, timeout=self._interval)
167
+
168
+ if healthy:
169
+ self._health_event.set()
170
+ if not was_healthy:
171
+ logger.warning(
172
+ "LMCache server is healthy again — resuming normal operation"
173
+ )
174
+ else:
175
+ self._health_event.clear()
176
+ if was_healthy:
177
+ logger.warning("LMCache server is unhealthy — entering degraded mode")
178
+
179
+ return ThreadRunSummary(
180
+ success=True,
181
+ message="healthy" if healthy else "unhealthy",
182
+ )
183
+
184
+
185
+ @dataclass
186
+ class LoadStoreOp:
187
+ token_ids: list[int]
188
+ """Token IDs for the load/store operation"""
189
+
190
+ block_ids: list[int]
191
+ """Block ids for the load/store operation"""
192
+
193
+ start: int = 0
194
+ """Start token index"""
195
+
196
+ end: int = 0
197
+ """End token index"""
198
+
199
+ skip_first_n_tokens: int = 0
200
+ """Number of tokens to skip writing at the beginning of the retrieve
201
+ range. Used to avoid overwriting APC-shared GPU blocks during retrieve."""
202
+
203
+ def __len__(self) -> int:
204
+ return len(self.block_ids)
205
+
206
+
207
+ StoreResult = bool
208
+ RetrieveResult = bool
209
+ LookupResult = int
210
+
211
+
212
+ class LMCacheMPSchedulerAdapter:
213
+ # Signature kept backward-compatible with the vllm-bundled caller
214
+ # at vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py,
215
+ # which still passes (world_size, kv_rank, vllm_block_size) positionally
216
+ # and `mq_timeout` as a kwarg. Pass `parallel_strategy` for the new form.
217
+ def __init__(
218
+ self,
219
+ server_url: str,
220
+ context: zmq.Context,
221
+ model_name: str,
222
+ world_size: int = 1,
223
+ kv_rank: int = 0,
224
+ vllm_block_size: int = 16,
225
+ tp_size: int = 1,
226
+ parallel_strategy: Optional[ParallelStrategy] = None,
227
+ mq_timeout: float = DEFAULT_MQ_TIMEOUT,
228
+ heartbeat_interval: float = DEFAULT_HEARTBEAT_INTERVAL,
229
+ ):
230
+ """
231
+ Args:
232
+ server_url: The server URL for the LMCache message queue
233
+ context: The ZMQ context
234
+ model_name: The model name used for LMCache keys
235
+ vllm_block_size: The block size used in vLLM
236
+ parallel_strategy:
237
+ The parallel strategy, which includes `use_mla`,
238
+ `kv_world_size`, `kv_worker_id` and so on. If None,
239
+ synthesised from world_size/kv_rank/tp_size.
240
+ mq_timeout: Timeout in seconds for message queue requests.
241
+ heartbeat_interval: Interval in seconds between heartbeat pings.
242
+ """
243
+ if parallel_strategy is None:
244
+ parallel_strategy = ParallelStrategy(
245
+ use_mla=False,
246
+ kv_world_size=world_size,
247
+ kv_worker_id=kv_rank,
248
+ actual_world_size=world_size,
249
+ actual_worker_id=kv_rank,
250
+ tp_size=tp_size,
251
+ pp_size=1,
252
+ )
253
+ self.mq_client = MessageQueueClient(server_url, context)
254
+ self._mq_timeout = mq_timeout
255
+
256
+ # Lookup state tracking:
257
+ # - _pending_lookups: request_ids submitted but not yet resolved
258
+ # - _finished_lookup_results: cached chunk count keyed by request_id,
259
+ # so that repeated calls to check_lookup_result return the same value
260
+ # even after the server has already popped the job (exactly-once).
261
+ self._pending_lookups: set[str] = set()
262
+ self._finished_lookup_results: dict[str, int] = {}
263
+
264
+ self.model_name = model_name
265
+ self.parallel_strategy = parallel_strategy
266
+
267
+ # Read chunk size from lmcache
268
+ try:
269
+ self.chunk_size = get_lmcache_chunk_size(self.mq_client)
270
+ except TimeoutError:
271
+ self.mq_client.close()
272
+ raise ConnectionError(
273
+ f"LMCache server did not respond within {mq_timeout}s. "
274
+ "Is the server running?"
275
+ ) from None
276
+ assert self.chunk_size % vllm_block_size == 0, (
277
+ "LMCache chunk size should be a multiple of vLLM block size"
278
+ )
279
+ self.blocks_in_chunk = self.chunk_size // vllm_block_size
280
+
281
+ # Health state (shared with heartbeat thread)
282
+ self._health_event = threading.Event()
283
+ self._health_event.set()
284
+
285
+ # Heartbeat thread is created but NOT started yet.
286
+ # It will be lazily started on the first lookup
287
+ # request, by which time vLLM is fully ready.
288
+ self._heartbeat_interval = heartbeat_interval
289
+ self._heartbeat: HeartbeatThread | None = None
290
+ self._heartbeat_lock = threading.Lock()
291
+
292
+ @property
293
+ def world_size(self) -> int:
294
+ """Get the kv world size."""
295
+ return self.parallel_strategy.kv_world_size
296
+
297
+ @property
298
+ def tp_size(self) -> int:
299
+ """The tensor parallel size."""
300
+ return self.parallel_strategy.tp_size
301
+
302
+ @property
303
+ def is_healthy(self) -> bool:
304
+ """Whether the LMCache server is healthy."""
305
+ return self._health_event.is_set()
306
+
307
+ def _ensure_heartbeat_started(self) -> None:
308
+ """Lazily start the heartbeat thread on first use."""
309
+ if self._heartbeat is not None:
310
+ return
311
+ with self._heartbeat_lock:
312
+ if self._heartbeat is not None:
313
+ return
314
+ self._heartbeat = HeartbeatThread(
315
+ mq_client=self.mq_client,
316
+ health_event=self._health_event,
317
+ interval=self._heartbeat_interval,
318
+ )
319
+ self._heartbeat.start()
320
+
321
+ @_lmcache_nvtx_annotate
322
+ def maybe_submit_lookup_request(
323
+ self,
324
+ request_id: str,
325
+ token_ids: list[int],
326
+ cache_salt: str = "",
327
+ ):
328
+ """
329
+ Submit a new lookup request to LMCache if there is no ongoing request.
330
+
331
+ Sends a LOOKUP request to the server and blocks until a prefetch
332
+ job ID is returned. The actual prefetch result can then be polled
333
+ via ``check_lookup_result``.
334
+
335
+ Args:
336
+ request_id: The ID of the lookup request. The same ID indicates it's
337
+ from the same request
338
+ token_ids: Token IDs to lookup from LMCache
339
+ cache_salt: Per-user isolation salt. Requests with different
340
+ cache_salt values produce separate cache entries.
341
+
342
+ Returns:
343
+ None
344
+
345
+ Notes:
346
+ This function will have a side-effect: submitting a look up request to
347
+ LMCache, which will essentially 'lock' the KV cache chunks in the LMCache
348
+ for later retrieve operations.
349
+ In the meantime, this function will record the lookup request, and the
350
+ status of the look up request can be checked by `check_lookup_result`.
351
+ """
352
+ self._ensure_heartbeat_started()
353
+
354
+ if not self.is_healthy:
355
+ return
356
+
357
+ if request_id in self._pending_lookups:
358
+ # Skip if there is already a lookup request
359
+ return
360
+
361
+ aligned_end = (len(token_ids) // self.chunk_size) * self.chunk_size
362
+
363
+ key = self._create_key(
364
+ token_ids,
365
+ start=0,
366
+ end=aligned_end,
367
+ request_id=request_id,
368
+ cache_salt=cache_salt,
369
+ ).no_worker_id_version()
370
+
371
+ future = send_lmcache_request(
372
+ self.mq_client,
373
+ RequestType.LOOKUP,
374
+ [key, self.tp_size],
375
+ )
376
+ try:
377
+ future.result(timeout=self._mq_timeout)
378
+ except TimeoutError:
379
+ logger.warning(
380
+ "LOOKUP request timed out after %ss. Marking server as unhealthy.",
381
+ self._mq_timeout,
382
+ )
383
+ self._health_event.clear()
384
+ return
385
+ self._pending_lookups.add(request_id)
386
+
387
+ @_lmcache_nvtx_annotate
388
+ def check_lookup_result(self, request_id: str) -> int | None:
389
+ """
390
+ Check the result of a previously submitted lookup request.
391
+
392
+ Sends a QUERY_PREFETCH_STATUS request to the server and blocks
393
+ until the server responds. Returns the matched token count
394
+ when the prefetch is complete, or None if still in progress.
395
+
396
+ Args:
397
+ request_id: The ID of the lookup request submitted in
398
+ `maybe_submit_lookup_request`
399
+
400
+ Returns:
401
+ An integer representing the total number of tokens matched
402
+ in LMCache (prefix matching), or
403
+ None if the lookup request is not finished yet.
404
+ """
405
+ if request_id not in self._pending_lookups:
406
+ # No job — either unhealthy at submit time or already cleaned up.
407
+ # If we have a cached result, return it to handle repeated calls.
408
+ return self._finished_lookup_results.get(request_id, 0)
409
+
410
+ if not self.is_healthy:
411
+ # Server went down — give up on this lookup
412
+ return 0
413
+
414
+ if request_id in self._finished_lookup_results:
415
+ # Return cached result if the job is already finished
416
+ return self._finished_lookup_results[request_id]
417
+
418
+ try:
419
+ result = send_lmcache_request(
420
+ self.mq_client,
421
+ RequestType.QUERY_PREFETCH_STATUS,
422
+ [request_id],
423
+ ).result(timeout=self._mq_timeout)
424
+ except TimeoutError:
425
+ logger.warning(
426
+ "QUERY_PREFETCH_STATUS timed out after %ss. "
427
+ "Marking server as unhealthy.",
428
+ self._mq_timeout,
429
+ )
430
+ self._health_event.clear()
431
+ return 0
432
+
433
+ if result is None:
434
+ return None
435
+
436
+ token_count = result * self.chunk_size
437
+ self._finished_lookup_results[request_id] = token_count
438
+ return token_count
439
+
440
+ def num_blocks_per_chunk(self) -> int:
441
+ """
442
+ Returns:
443
+ The number of vllm blocks in a LMCache data chunk
444
+ """
445
+ return self.blocks_in_chunk
446
+
447
+ def cleanup_lookup_result(self, request_id: str) -> None:
448
+ """
449
+ Clean up lookup state for a finished request to prevent memory leak.
450
+ Args:
451
+ request_id: The ID of the finished request.
452
+ """
453
+ self._pending_lookups.discard(request_id)
454
+ self._finished_lookup_results.pop(request_id, None)
455
+
456
+ def free_lookup_locks(
457
+ self,
458
+ token_ids: list[int],
459
+ start: int,
460
+ end: int,
461
+ request_id: str,
462
+ cache_salt: str = "",
463
+ ) -> None:
464
+ """Release read locks acquired during lookup without a full retrieve.
465
+
466
+ Use this when some chunks matched by lookup overlap with blocks that
467
+ vLLM has already computed, so they will never be retrieved. Calling
468
+ this prevents those chunks from holding read locks until TTL expiry.
469
+
470
+ Or use this when a request is cancelled or aborted after lookup but
471
+ before retrieve to avoid holding read locks until TTL expiry.
472
+
473
+ When ``start`` or ``end`` is not aligned to the chunk size, the
474
+ entire chunk containing start boundary is freed but not end boundary.
475
+ It is caller's responsibility to properly align the boundaries.
476
+
477
+ Args:
478
+ token_ids: Token IDs for the key (same as used in lookup).
479
+ start: Start token index.
480
+ end: End token index.
481
+ request_id: The request ID.
482
+ cache_salt: Per-user isolation salt.
483
+ """
484
+ if not self.is_healthy:
485
+ return
486
+
487
+ key = self._create_key(
488
+ token_ids,
489
+ start=start,
490
+ end=end,
491
+ request_id=request_id,
492
+ cache_salt=cache_salt,
493
+ ).no_worker_id_version()
494
+ send_lmcache_request(
495
+ self.mq_client,
496
+ RequestType.FREE_LOOKUP_LOCKS,
497
+ [key, self.tp_size],
498
+ )
499
+
500
+ def end_session(self, request_id: str) -> None:
501
+ """
502
+ Notify LMCache server to remove the session for a finished request.
503
+ Args:
504
+ request_id: The ID of the finished request.
505
+ """
506
+ if not self.is_healthy:
507
+ return
508
+
509
+ send_lmcache_request(
510
+ self.mq_client,
511
+ RequestType.END_SESSION,
512
+ [request_id],
513
+ )
514
+
515
+ def report_block_allocations(
516
+ self,
517
+ records: list[BlockAllocationRecord],
518
+ ) -> None:
519
+ """Report vLLM GPU block allocation deltas to LMCache server.
520
+
521
+ Fire-and-forget: does not wait for a response. If the server
522
+ is unhealthy the report is silently dropped.
523
+
524
+ Args:
525
+ records: List of BlockAllocationRecord with per-request
526
+ block and token allocation deltas.
527
+ """
528
+ if not self.is_healthy or not records:
529
+ return
530
+
531
+ send_lmcache_request(
532
+ self.mq_client,
533
+ RequestType.REPORT_BLOCK_ALLOCATION,
534
+ [os.getpid(), self.model_name, records],
535
+ )
536
+
537
+ # Helper functions
538
+ def _create_key(
539
+ self,
540
+ token_ids: list[int],
541
+ start: int,
542
+ end: int,
543
+ request_id: str,
544
+ cache_salt: str = "",
545
+ ) -> IPCCacheEngineKey:
546
+ """Convert token IDs to an IPC cache engine key.
547
+
548
+ Args:
549
+ token_ids: The token IDs.
550
+ start: Start token index.
551
+ end: End token index.
552
+ request_id: The request ID.
553
+ cache_salt: Per-user isolation salt.
554
+
555
+ Returns:
556
+ IPCCacheEngineKey: The constructed key.
557
+ """
558
+ # NOTE: for the scheduler adapter, we don't have a worker id,
559
+ # so we set it to None in the key.
560
+ return IPCCacheEngineKey(
561
+ model_name=self.model_name,
562
+ world_size=self.world_size,
563
+ worker_id=None,
564
+ token_ids=tuple(token_ids),
565
+ start=start,
566
+ end=end,
567
+ request_id=request_id,
568
+ cache_salt=cache_salt,
569
+ )
570
+
571
+
572
+ class LMCacheMPWorkerAdapter:
573
+ # Signature kept backward-compatible with the vllm-bundled caller; see
574
+ # LMCacheMPSchedulerAdapter above for details.
575
+ def __init__(
576
+ self,
577
+ server_url: str,
578
+ context: zmq.Context,
579
+ model_name: str,
580
+ world_size: int = 1,
581
+ kv_rank: int = 0,
582
+ vllm_block_size: int = 16,
583
+ tp_size: int = 1,
584
+ parallel_strategy: Optional[ParallelStrategy] = None,
585
+ mq_timeout: float = DEFAULT_MQ_TIMEOUT,
586
+ heartbeat_interval: float = DEFAULT_HEARTBEAT_INTERVAL,
587
+ ):
588
+ if parallel_strategy is None:
589
+ parallel_strategy = ParallelStrategy(
590
+ use_mla=False,
591
+ kv_world_size=world_size,
592
+ kv_worker_id=kv_rank,
593
+ actual_world_size=world_size,
594
+ actual_worker_id=kv_rank,
595
+ tp_size=tp_size,
596
+ pp_size=1,
597
+ )
598
+ self.mq_client = MessageQueueClient(server_url, context)
599
+ self._mq_timeout = mq_timeout
600
+
601
+ # Instance id for GPU worker
602
+ self.instance_id = os.getpid()
603
+
604
+ # Registered kv caches from vLLM
605
+ self.kv_caches: dict[str, torch.Tensor] = {}
606
+
607
+ # Request futures
608
+ self.store_futures: dict[str, MessagingFuture[StoreResult]] = {}
609
+ # request_id -> (future, block_ids)
610
+ self.retrieve_futures: dict[
611
+ str, tuple[MessagingFuture[RetrieveResult], list[int]]
612
+ ] = {}
613
+
614
+ # Block IDs that failed due to retrieve timeout
615
+ self.error_block_ids: set[int] = set()
616
+
617
+ # The store requests that have finished execution in LMCache
618
+ self.finished_stores: set[str] = set()
619
+ # The finished request ids that are passed via vLLM and also
620
+ # have corresponding store requests submitted to LMCache before
621
+ self.previously_finished: set[str] = set()
622
+ # Request IDs already returned as finished_sending to the scheduler.
623
+ # Prevents re-reporting the same ID after drain clears tracking sets.
624
+ self._returned_finished: set[str] = set()
625
+
626
+ self.model_name = model_name
627
+ self.parallel_strategy = parallel_strategy
628
+
629
+ # Read chunk size from lmcache
630
+ try:
631
+ chunk_size = get_lmcache_chunk_size(self.mq_client)
632
+ except TimeoutError:
633
+ self.mq_client.close()
634
+ raise ConnectionError(
635
+ f"LMCache server did not respond within {mq_timeout}s. "
636
+ "Is the server running?"
637
+ ) from None
638
+ assert chunk_size % vllm_block_size == 0, (
639
+ "LMCache chunk size should be a multiple of vLLM block size"
640
+ )
641
+ self.blocks_in_chunk = chunk_size // vllm_block_size
642
+
643
+ # Health state (shared with heartbeat thread)
644
+ self._health_event = threading.Event()
645
+ self._health_event.set()
646
+
647
+ # Heartbeat thread is created but NOT started yet.
648
+ # It will be lazily started on the first store or retrieve
649
+ # request, by which time vLLM is fully ready (model loaded,
650
+ # KV caches allocated, warmup & CUDA graph capture done).
651
+ self._heartbeat_interval = heartbeat_interval
652
+ self._heartbeat: HeartbeatThread | None = None
653
+ self._heartbeat_lock = threading.Lock()
654
+
655
+ # request telemetry, used for prefill-decode disagg
656
+ # TODO: pass down the configuration via vLLM connector config
657
+ # instead of env var
658
+ self.request_telemetry = RequestTelemetryFactory.create(
659
+ telemetry_type=os.getenv("LMCACHE_REQUEST_TELEMETRY_TYPE", "noop"),
660
+ config={
661
+ "endpoint": os.getenv(
662
+ "LMCACHE_REQUEST_TELEMETRY_ENDPOINT",
663
+ "http://localhost:5768/api/v1/telemetry",
664
+ ),
665
+ },
666
+ )
667
+
668
+ @property
669
+ def is_healthy(self) -> bool:
670
+ """Whether the LMCache server is healthy."""
671
+ return self._health_event.is_set()
672
+
673
+ @property
674
+ def world_size(self) -> int:
675
+ """Get the kv world size."""
676
+ return self.parallel_strategy.kv_world_size
677
+
678
+ @property
679
+ def worker_id(self) -> int:
680
+ """Get the kv worker id."""
681
+ return self.parallel_strategy.kv_worker_id
682
+
683
+ @property
684
+ def use_mla(self) -> bool:
685
+ """Whether to use MLA."""
686
+ return self.parallel_strategy.use_mla
687
+
688
+ @property
689
+ def is_first_rank_of_pp_group(self) -> bool:
690
+ """Is the first rank of the pipeline parallel group."""
691
+ return (
692
+ self.parallel_strategy.actual_worker_id % self.parallel_strategy.tp_size
693
+ == 0
694
+ )
695
+
696
+ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
697
+ """
698
+ Register the kv caches with LMCache server
699
+
700
+ Args:
701
+ kv_caches: A dict of kv caches to register. The keys are the
702
+ layer names and the values are the corresponding tensors.
703
+ """
704
+ # First Party
705
+ from lmcache.integration.vllm.utils import vllm_layout_hints
706
+ from lmcache.v1.gpu_connector.utils import (
707
+ ensure_contiguous_kv_caches,
708
+ )
709
+
710
+ # Register kv cache and send the request
711
+ logger.info("Registering kv caches")
712
+
713
+ layout_hints = vllm_layout_hints()
714
+ kv_caches = ensure_contiguous_kv_caches(
715
+ kv_caches, kv_layout=layout_hints.get("kv_layout")
716
+ )
717
+
718
+ self.kv_caches = kv_caches
719
+
720
+ future = send_lmcache_request(
721
+ self.mq_client,
722
+ RequestType.REGISTER_KV_CACHE,
723
+ [
724
+ self.instance_id,
725
+ wrap_kv_caches(kv_caches),
726
+ self.model_name,
727
+ self.world_size,
728
+ layout_hints,
729
+ ],
730
+ )
731
+ try:
732
+ future.result(timeout=self._mq_timeout)
733
+ except TimeoutError:
734
+ raise ConnectionError(
735
+ "LMCache server did not respond to "
736
+ "register_kv_caches within "
737
+ f"{self._mq_timeout}s. Is the server running?"
738
+ ) from None
739
+
740
+ def _ensure_heartbeat_started(self) -> None:
741
+ """Lazily start the heartbeat thread on first use."""
742
+ if self._heartbeat is not None:
743
+ return
744
+ with self._heartbeat_lock:
745
+ if self._heartbeat is not None:
746
+ return
747
+ self._heartbeat = HeartbeatThread(
748
+ mq_client=self.mq_client,
749
+ health_event=self._health_event,
750
+ interval=self._heartbeat_interval,
751
+ )
752
+ self._heartbeat.start()
753
+
754
+ @_lmcache_nvtx_annotate
755
+ def submit_store_request(
756
+ self,
757
+ request_id: str,
758
+ op: LoadStoreOp,
759
+ event: torch.cuda.Event,
760
+ cache_salt: str = "",
761
+ ):
762
+ """
763
+ Submit a KV cache store request to LMCache
764
+
765
+ Args:
766
+ request_id: The ID of the request
767
+ op: The LoadStoreOp describing the store operation.
768
+ event: The CUDA event that is recorded after the current
769
+ model inference step
770
+ cache_salt: Per-user isolation salt.
771
+ """
772
+ self._ensure_heartbeat_started()
773
+
774
+ if not self.is_healthy:
775
+ return
776
+
777
+ assert op.token_ids is not None
778
+ key = self._create_key(
779
+ op.token_ids,
780
+ op.start,
781
+ op.end,
782
+ request_id=request_id,
783
+ cache_salt=cache_salt,
784
+ )
785
+ future = send_lmcache_request(
786
+ self.mq_client,
787
+ RequestType.STORE,
788
+ [key, self.instance_id, op.block_ids, event.ipc_handle()],
789
+ ).to_cuda_future()
790
+ self.store_futures[request_id] = future
791
+
792
+ @_lmcache_nvtx_annotate
793
+ def submit_retrieve_request(
794
+ self,
795
+ request_id: str,
796
+ op: LoadStoreOp,
797
+ event: torch.cuda.Event,
798
+ cache_salt: str = "",
799
+ ):
800
+ """
801
+ Submit a KV cache retrieve request to LMCache
802
+
803
+ Args:
804
+ request_id: The ID of the request
805
+ op: The LoadStoreOp describing the retrieve operation.
806
+ event: The CUDA event that is recorded after the current
807
+ model inference step
808
+ cache_salt: Per-user isolation salt.
809
+ """
810
+ self._ensure_heartbeat_started()
811
+
812
+ if not self.is_healthy:
813
+ self.error_block_ids.update(op.block_ids)
814
+ return
815
+
816
+ assert op.token_ids is not None
817
+ key = self._create_key(
818
+ op.token_ids,
819
+ op.start,
820
+ op.end,
821
+ request_id=request_id,
822
+ cache_salt=cache_salt,
823
+ )
824
+ future = send_lmcache_request(
825
+ self.mq_client,
826
+ RequestType.RETRIEVE,
827
+ [
828
+ key,
829
+ self.instance_id,
830
+ op.block_ids,
831
+ event.ipc_handle(),
832
+ op.skip_first_n_tokens,
833
+ ],
834
+ ).to_cuda_future()
835
+ self.retrieve_futures[request_id] = (future, list(op.block_ids))
836
+
837
+ @_lmcache_nvtx_annotate
838
+ def batched_submit_store_requests(
839
+ self,
840
+ request_ids: list[str],
841
+ ops: list[LoadStoreOp],
842
+ event: torch.cuda.Event,
843
+ cache_salts: list[str] | None = None,
844
+ ):
845
+ """
846
+ Submit a batched store request to LMCache
847
+
848
+ Args:
849
+ request_ids: The IDs of the requests
850
+ ops: The LoadStoreOps describing the store operations. Should have
851
+ the same length as request_ids
852
+ event: The CUDA event that is recorded after the current
853
+ model inference step
854
+ cache_salts: Per-user isolation salts, one per request. If None,
855
+ all requests use cache_salt="". The list length should be the same as
856
+ request_ids.
857
+ """
858
+ if cache_salts is None:
859
+ cache_salts = [""] * len(request_ids)
860
+ for request_id, op, salt in zip(request_ids, ops, cache_salts, strict=False):
861
+ self.submit_store_request(request_id, op, event, cache_salt=salt)
862
+
863
+ @_lmcache_nvtx_annotate
864
+ def batched_submit_retrieve_requests(
865
+ self,
866
+ request_ids: list[str],
867
+ ops: list[LoadStoreOp],
868
+ event: torch.cuda.Event,
869
+ cache_salts: list[str] | None = None,
870
+ ):
871
+ """
872
+ Submit a batched retrieve request to LMCache
873
+
874
+ Args:
875
+ request_ids: The IDs of the requests
876
+ ops: The LoadStoreOps describing the retrieve operations. Should have
877
+ the same length as request_ids
878
+ event: The CUDA event that is recorded after the current
879
+ model inference step
880
+ cache_salts: Per-user isolation salts, one per request. If None,
881
+ all requests use cache_salt="". The list length should be same as
882
+ request_ids.
883
+ """
884
+ if cache_salts is None:
885
+ cache_salts = [""] * len(request_ids)
886
+ for request_id, op, salt in zip(request_ids, ops, cache_salts, strict=False):
887
+ self.submit_retrieve_request(request_id, op, event, cache_salt=salt)
888
+
889
+ def _process_finished_stores(
890
+ self,
891
+ finished_req_ids_from_lmcache: set[str],
892
+ finished_req_ids_from_engine: set[str],
893
+ ) -> set[str]:
894
+ """Merge LMCache-side and engine-side finished store info."""
895
+ self.finished_stores.update(finished_req_ids_from_lmcache)
896
+ ret_stores = set()
897
+ for req_id in finished_req_ids_from_engine:
898
+ if req_id in self._returned_finished:
899
+ continue
900
+ if req_id in self.finished_stores or req_id in self.store_futures:
901
+ self.previously_finished.add(req_id)
902
+ else:
903
+ ret_stores.add(req_id)
904
+ ret_stores.update(self._update_and_get_finished_store())
905
+ self._returned_finished.update(ret_stores)
906
+ return ret_stores
907
+
908
+ @_lmcache_nvtx_annotate
909
+ def get_finished(
910
+ self, finished_req_ids_from_engine: set[str]
911
+ ) -> tuple[set[str] | None, set[str] | None]:
912
+ """
913
+ Check and get the finished store and retrieve requests.
914
+
915
+ Args:
916
+ finished_req_ids_from_engine: the set of request ids that are
917
+ reported as finished from the vLLM engine side.
918
+
919
+ Returns:
920
+ A tuple of two sets:
921
+ - The first set contains the finished store request ids. The returned
922
+ store request ids MUST be seen before in the
923
+ `finished_req_ids_from_engine`.
924
+ - The second set contains the finished retrieve request ids.
925
+
926
+ Notes:
927
+ When enabling async scheduling in vLLM, the same request ID may appear
928
+ multiple times in `finished_req_ids_from_engine`. The adapter should
929
+ take care of deduplicating the request IDs and only return the request
930
+ IDs that have not been returned before.
931
+ """
932
+ # If unhealthy, drain all pending futures immediately
933
+ if not self.is_healthy:
934
+ finished_stores = set(self.store_futures.keys())
935
+ finished_retrieves = set()
936
+ for request_id, (
937
+ _r_future,
938
+ r_block_ids,
939
+ ) in self.retrieve_futures.items():
940
+ finished_retrieves.add(request_id)
941
+ self.error_block_ids.update(r_block_ids)
942
+ self.store_futures.clear()
943
+ self.retrieve_futures.clear()
944
+
945
+ ret_stores = self._process_finished_stores(
946
+ finished_stores, finished_req_ids_from_engine
947
+ )
948
+ # A request may have a pending retrieve AND appear in
949
+ # finished_req_ids_from_engine (it ran without loading KV after
950
+ # the server died). The scheduler processes finished_recving
951
+ # first and deletes the request, so we must not also report it
952
+ # in finished_sending.
953
+ ret_stores -= finished_retrieves
954
+ return ret_stores, finished_retrieves
955
+
956
+ finished_stores = set()
957
+ finished_retrieves = set()
958
+ for request_id, s_future in self.store_futures.items():
959
+ if not s_future.query():
960
+ continue
961
+
962
+ s_result = s_future.result()
963
+ finished_stores.add(request_id)
964
+
965
+ if not s_result:
966
+ logger.error(
967
+ "Something went wrong when processing the "
968
+ "store request for request_id=%s",
969
+ request_id,
970
+ )
971
+
972
+ for request_id, (r_future, _) in self.retrieve_futures.items():
973
+ if not r_future.query():
974
+ continue
975
+
976
+ r_result = r_future.result()
977
+ finished_retrieves.add(request_id)
978
+
979
+ if not r_result:
980
+ logger.error(
981
+ "Something went wrong when processing the "
982
+ "retrieve request for request_id=%s, result=%s",
983
+ request_id,
984
+ r_result,
985
+ )
986
+
987
+ # Remove the finished requests from the tracking dicts
988
+ for request_id in finished_stores:
989
+ self.store_futures.pop(request_id, None)
990
+ for request_id in finished_retrieves:
991
+ self.retrieve_futures.pop(request_id, None)
992
+
993
+ # Update the internal states
994
+ ret_stores = self._process_finished_stores(
995
+ finished_stores, finished_req_ids_from_engine
996
+ )
997
+
998
+ # the invocation of `get_finished` means that
999
+ # these requests' KV caches are already fully stored.
1000
+ # or the requests normally ends without any store.
1001
+ if ret_stores:
1002
+ self.request_telemetry.on_request_store_finished(
1003
+ request_ids_set=ret_stores,
1004
+ model_name=self.model_name,
1005
+ world_size=self.world_size,
1006
+ kv_rank=self.worker_id,
1007
+ )
1008
+
1009
+ return ret_stores, finished_retrieves
1010
+
1011
+ def num_blocks_per_chunk(self) -> int:
1012
+ """
1013
+ Returns:
1014
+ The number of vllm blocks in a LMCache data chunk
1015
+ """
1016
+ return self.blocks_in_chunk
1017
+
1018
+ def get_block_ids_with_load_errors(self) -> set[int]:
1019
+ """
1020
+ Returns the block IDs that failed due to retrieve timeout,
1021
+ then clears the internal set.
1022
+ """
1023
+ errors = self.error_block_ids.copy()
1024
+ self.error_block_ids.clear()
1025
+ return errors
1026
+
1027
+ def shutdown(self):
1028
+ """
1029
+ Shutdown the LMCache MP worker adapter
1030
+ """
1031
+ logger.info("Unregistering kv caches")
1032
+ try:
1033
+ send_lmcache_request(
1034
+ self.mq_client,
1035
+ RequestType.UNREGISTER_KV_CACHE,
1036
+ [self.instance_id],
1037
+ ).result(timeout=self._mq_timeout)
1038
+ except TimeoutError:
1039
+ logger.warning(
1040
+ "LMCache server did not respond to unregister within %ss. "
1041
+ "Proceeding with shutdown.",
1042
+ self._mq_timeout,
1043
+ )
1044
+
1045
+ self.mq_client.close()
1046
+ self.request_telemetry.close()
1047
+
1048
+ # Helper functions
1049
+ def _update_and_get_finished_store(
1050
+ self,
1051
+ ) -> set[str]:
1052
+ """Converge the internal states about finished stores
1053
+ and returns the 'safe finished store request ids' back
1054
+ """
1055
+ safe_finished_s = self.finished_stores.intersection(self.previously_finished)
1056
+ self.finished_stores.difference_update(self.previously_finished)
1057
+ self.previously_finished.difference_update(safe_finished_s)
1058
+
1059
+ return safe_finished_s
1060
+
1061
+ def _create_key(
1062
+ self,
1063
+ token_ids: list[int],
1064
+ start: int,
1065
+ end: int,
1066
+ request_id: str,
1067
+ cache_salt: str = "",
1068
+ ) -> IPCCacheEngineKey:
1069
+ """Convert token IDs to an IPC cache engine key.
1070
+
1071
+ Args:
1072
+ token_ids: The token IDs.
1073
+ start: Start token index.
1074
+ end: End token index.
1075
+ request_id: The request ID.
1076
+ cache_salt: Per-user isolation salt.
1077
+
1078
+ Returns:
1079
+ IPCCacheEngineKey: The constructed key.
1080
+ """
1081
+ return IPCCacheEngineKey(
1082
+ model_name=self.model_name,
1083
+ world_size=self.world_size,
1084
+ worker_id=self.worker_id,
1085
+ token_ids=tuple(token_ids),
1086
+ start=start,
1087
+ end=end,
1088
+ request_id=request_id,
1089
+ cache_salt=cache_salt,
1090
+ )