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,1134 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from dataclasses import dataclass
4
+ from functools import partial
5
+ from itertools import islice
6
+ from typing import Generator
7
+ import argparse
8
+ import threading
9
+ import time
10
+
11
+ # Third Party
12
+ import torch
13
+ import zmq
14
+
15
+ # First Party
16
+ from lmcache.logging import init_logger
17
+ from lmcache.utils import _lmcache_nvtx_annotate
18
+ from lmcache.v1.distributed.api import (
19
+ MemoryLayoutDesc,
20
+ ObjectKey,
21
+ ipc_key_to_object_keys,
22
+ )
23
+ from lmcache.v1.distributed.config import (
24
+ StorageManagerConfig,
25
+ add_storage_manager_args,
26
+ parse_args_to_config,
27
+ )
28
+ from lmcache.v1.distributed.storage_manager import PrefetchHandle, StorageManager
29
+ from lmcache.v1.gpu_connector.gpu_ops import (
30
+ lmcache_memcpy_async_d2h,
31
+ lmcache_memcpy_async_h2d,
32
+ )
33
+ from lmcache.v1.gpu_connector.utils import LayoutHints
34
+ from lmcache.v1.memory_management import MemoryObj
35
+ from lmcache.v1.mp_observability.config import (
36
+ ObservabilityConfig,
37
+ add_observability_args,
38
+ init_observability,
39
+ parse_args_to_observability_config,
40
+ )
41
+ from lmcache.v1.mp_observability.event import Event, EventType
42
+ from lmcache.v1.mp_observability.event_bus import get_event_bus
43
+ from lmcache.v1.mp_observability.otel_init import register_gauge
44
+ from lmcache.v1.mp_observability.trace import maybe_initialize_trace_recorder
45
+ from lmcache.v1.multiprocess.config import (
46
+ MPServerConfig,
47
+ add_mp_server_args,
48
+ parse_args_to_mp_server_config,
49
+ )
50
+ from lmcache.v1.multiprocess.custom_types import (
51
+ BlockAllocationRecord,
52
+ IPCCacheEngineKey,
53
+ KVCache,
54
+ )
55
+ from lmcache.v1.multiprocess.gpu_context import (
56
+ GPUCacheContext,
57
+ )
58
+ from lmcache.v1.multiprocess.mq import MessageQueueServer
59
+ from lmcache.v1.multiprocess.protocol import (
60
+ RequestType,
61
+ get_handler_type,
62
+ get_payload_classes,
63
+ )
64
+ from lmcache.v1.multiprocess.session import SessionManager
65
+ from lmcache.v1.multiprocess.token_hasher import TokenHasher
66
+ import lmcache.c_ops as lmc_ops
67
+
68
+ logger = init_logger(__name__)
69
+
70
+
71
+ # Helper functions
72
+ def compute_extra_count(
73
+ tp_size: int,
74
+ world_size: int,
75
+ ) -> int:
76
+ """Compute extra count for MLA multi-reader locking.
77
+
78
+ Non-MLA: each TP worker owns a distinct KV shard,
79
+ so each ObjectKey is retrieved by exactly 1
80
+ worker -> extra_count = 0.
81
+ MLA: TP does not split KV caches, all TP workers
82
+ share the same object. vLLM passes world_size
83
+ already divided by tp_size (e.g. world_size=1
84
+ for TP=4 PP=1), so ipc_keys_to_object_keys
85
+ only produces 1 ObjectKey per chunk. All TP
86
+ workers retrieve that same ObjectKey, hence
87
+ extra_count = tp_size - 1.
88
+
89
+ Detection: tp > world_size means MLA (world_size
90
+ was divided by tp on the vLLM side).
91
+
92
+ Fallback: old vLLM (<= 0.8.5) does not send
93
+ tp_size (defaults to 1); we fall back to
94
+ world_size which gives extra_count = 0
95
+ (safe but may under-lock for MLA).
96
+
97
+ TODO: world_size currently carries an overloaded
98
+ meaning (total ranks for non-MLA vs total/tp for
99
+ MLA). Consider a dedicated field in the future.
100
+
101
+ Args:
102
+ tp_size: Tensor-parallel size from the client.
103
+ world_size: World size from the cache key.
104
+
105
+ Returns:
106
+ Number of extra count (0 for non-MLA).
107
+ """
108
+ tp = tp_size if tp_size > 1 else world_size
109
+ return tp - 1 if tp > world_size else 0
110
+
111
+
112
+ def get_layout_desc(gpu_context: GPUCacheContext, num_tokens: int) -> MemoryLayoutDesc:
113
+ """Get the memory layout description for a given GPU context and number of tokens.
114
+
115
+ Supports multiple KV layer groups with different shapes and dtypes.
116
+
117
+ Args:
118
+ gpu_context: The GPU cache context containing the KV cache information.
119
+ num_tokens: The number of tokens to determine the layout for.
120
+
121
+ Returns:
122
+ MemoryLayoutDesc: The memory layout description containing shapes and dtypes.
123
+ """
124
+ num_groups = gpu_context.kv_layer_groups_manager.num_groups
125
+ shapes = [
126
+ gpu_context.get_kv_buffer_shape(num_tokens, group_idx)
127
+ for group_idx in range(num_groups)
128
+ ]
129
+ dtypes = [
130
+ gpu_context.kv_layer_groups_manager.kv_layer_groups[group_idx].dtype
131
+ for group_idx in range(num_groups)
132
+ ]
133
+ return MemoryLayoutDesc(shapes=shapes, dtypes=dtypes)
134
+
135
+
136
+ def batched_iteration(lst: list, batch_size: int) -> Generator[tuple, None, None]:
137
+ """Utility function to iterate over a list in batches.
138
+
139
+ Args:
140
+ lst: The list to iterate over.
141
+ batch_size: The size of each batch.
142
+
143
+ Yields:
144
+ Batches of the list as tuples.
145
+ """
146
+ if batch_size < 1:
147
+ raise ValueError("batch size must be at least one")
148
+ it = iter(lst)
149
+ while batch := tuple(islice(it, batch_size)):
150
+ yield batch
151
+
152
+
153
+ @dataclass
154
+ class _PrefetchJob:
155
+ handle: PrefetchHandle
156
+ world_size: int
157
+ request_id: str
158
+
159
+
160
+ # Main class for the mp cache engine
161
+ class MPCacheEngine:
162
+ def __init__(
163
+ self,
164
+ storage_manager_config: StorageManagerConfig,
165
+ chunk_size: int = 256,
166
+ hash_algorithm: str = "blake3",
167
+ ):
168
+ # GPU ID -> KV cache tensors
169
+ self.gpu_contexts: dict[int, GPUCacheContext] = {}
170
+
171
+ # GPU ID -> (model name, world size) as metadata
172
+ # NOTE: This is mainly for determining the layout desc during prefetch
173
+ # We assume that if the (model name, world size) is the same, then
174
+ # the layout desc returned by the gpu context is the same.
175
+ self.gpu_context_meta: dict[int, tuple[str, int]] = {}
176
+
177
+ # chunk size
178
+ self.chunk_size = chunk_size
179
+
180
+ # Lock for clear() to avoid concurrent storage manager mutations
181
+ self.lock = threading.Lock()
182
+
183
+ # storage manager
184
+ self.storage_manager = StorageManager(storage_manager_config)
185
+
186
+ # Token hasher and session manager for token-based operations
187
+ self.token_hasher = TokenHasher(
188
+ chunk_size=chunk_size, hash_algorithm=hash_algorithm
189
+ )
190
+ self.session_manager = SessionManager(self.token_hasher)
191
+
192
+ # EventBus for observability
193
+ self._event_bus = get_event_bus()
194
+
195
+ # Prefetch job tracking for two-phase lookup, keyed by request_id.
196
+ # TODO: implement periodic cleanup of stale _prefetch_jobs entries
197
+ # for crash resilience (e.g., client calls lookup but never queries)
198
+ self._prefetch_jobs: dict[str, _PrefetchJob] = {}
199
+ self._prefetch_job_lock = threading.Lock()
200
+
201
+ self._setup_metrics()
202
+
203
+ def register_kv_cache(
204
+ self,
205
+ instance_id: int,
206
+ kv_caches: KVCache,
207
+ model_name: str,
208
+ world_size: int,
209
+ layout_hints: LayoutHints,
210
+ ) -> None:
211
+ """
212
+ Registers the KV cache tensors for a given GPU instance ID.
213
+
214
+ Args:
215
+ instance_id (int): The GPU instance ID (such as PID).
216
+ kv_caches (KVCache): The KV cache tensor wrappers from vLLM.
217
+ model_name (str): The name of the model associated with this KV cache.
218
+ world_size (int): The world size associated with this KV cache.
219
+ layout_hints: See :class:`LayoutHints`. Forwarded to
220
+ :class:`GPUCacheContext` for GPU KV format detection.
221
+ """
222
+ gpu_context = GPUCacheContext(
223
+ kv_caches,
224
+ self.chunk_size,
225
+ layout_hints=layout_hints or None,
226
+ )
227
+ self.gpu_contexts[instance_id] = gpu_context
228
+ self.gpu_context_meta[instance_id] = (model_name, world_size)
229
+ logger.info(
230
+ "Registered KV cache for GPU ID %d with %d layers",
231
+ instance_id,
232
+ gpu_context.num_layers,
233
+ )
234
+
235
+ def unregister_kv_cache(self, instance_id: int) -> None:
236
+ """
237
+ Unregisters the KV cache tensors for a given GPU instance ID.
238
+
239
+ Args:
240
+ instance_id (int): The GPU instance ID (such as PID).
241
+ """
242
+ if instance_id in self.gpu_contexts:
243
+ del self.gpu_contexts[instance_id]
244
+ del self.gpu_context_meta[instance_id]
245
+ logger.info("Unregistered KV cache for GPU ID %d", instance_id)
246
+ torch.cuda.empty_cache()
247
+ else:
248
+ logger.warning("No KV cache found for GPU ID %d to unregister", instance_id)
249
+
250
+ @_lmcache_nvtx_annotate
251
+ def store(
252
+ self,
253
+ key: IPCCacheEngineKey,
254
+ instance_id: int,
255
+ gpu_block_ids: list[int],
256
+ event_ipc_handle: bytes,
257
+ ) -> tuple[bytes, bool]:
258
+ """
259
+ Stores the GPU KV cache blocks to CPU.
260
+
261
+ Args:
262
+ key (IPCCacheEngineKey): The IPC key for the KV cache blocks.
263
+ Must have worker_id != None (worker store operation).
264
+ instance_id (int): The GPU instance ID (such as PID).
265
+ gpu_block_ids (list[int]): The GPU block IDs to store.
266
+ event_ipc_handle (bytes): The IPC handle of the event to wait on.
267
+
268
+ Returns:
269
+ tuple[bytes, bool]: The first element is the IPC handle of the event
270
+ that signals the completion of the store operation. The second
271
+ element indicates whether the store operation was successful.
272
+ """
273
+ session = self.session_manager.get_or_create(key.request_id)
274
+ session.set_tokens(list(key.token_ids))
275
+ chunk_hashes = [
276
+ TokenHasher.hash_to_bytes(h) for h in session.get_hashes(key.start, key.end)
277
+ ]
278
+
279
+ st = time.perf_counter()
280
+
281
+ assert key.worker_id is not None, "Must store with worker_id != None"
282
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
283
+
284
+ assert instance_id in self.gpu_contexts, (
285
+ f"KV cache not registered for GPU ID {instance_id}"
286
+ )
287
+ gpu_context = self.gpu_contexts[instance_id]
288
+
289
+ blocks_per_chunk = self.chunk_size // gpu_context.block_size
290
+
291
+ with (
292
+ torch.cuda.device(gpu_context.device),
293
+ torch.cuda.stream(gpu_context.stream),
294
+ ):
295
+ event = torch.cuda.Event(interprocess=True)
296
+
297
+ # Stage all block_ids to GPU once before the loop
298
+ all_block_ids_gpu = gpu_context.stage_block_ids(gpu_block_ids)
299
+
300
+ # Wait for vLLM to finish
301
+ vllm_event = torch.cuda.Event.from_ipc_handle(
302
+ gpu_context.device, event_ipc_handle
303
+ )
304
+ vllm_event.wait(stream=gpu_context.stream)
305
+
306
+ # CPU-synchronous sentinel: a GPU store is about to be enqueued.
307
+ # Must be published via publish() (not publish_on_stream) so the
308
+ # drain thread sees it before MP_SESSION_END can race MP_STORE_END.
309
+ self._event_bus.publish(
310
+ Event(
311
+ event_type=EventType.MP_STORE_SUBMITTED,
312
+ session_id=key.request_id,
313
+ metadata={"device": str(gpu_context.device)},
314
+ )
315
+ )
316
+
317
+ self._event_bus.publish_on_stream(
318
+ gpu_context.cupy_stream,
319
+ Event(
320
+ event_type=EventType.MP_STORE_START,
321
+ session_id=key.request_id,
322
+ metadata={"device": str(gpu_context.device)},
323
+ ),
324
+ )
325
+
326
+ reserved_dict: dict = {}
327
+ try:
328
+ layout_desc = get_layout_desc(gpu_context, self.chunk_size)
329
+ reserved_dict = self.storage_manager.reserve_write(
330
+ obj_keys, layout_desc, "new"
331
+ )
332
+
333
+ # NOTE: Store is not batched because some obj_keys may be
334
+ # skipped (not in reserved_dict), making block_ids
335
+ # non-contiguous. Batching would require torch.cat to
336
+ # reassemble block_ids, negating the benefit.
337
+ num_groups = gpu_context.kv_layer_groups_manager.num_groups
338
+ for idx, obj_key in enumerate(obj_keys):
339
+ if obj_key in reserved_dict:
340
+ memory_obj = reserved_dict[obj_key]
341
+ else:
342
+ continue
343
+
344
+ chunk_block_ids_gpu = all_block_ids_gpu[
345
+ idx * blocks_per_chunk : (idx + 1) * blocks_per_chunk
346
+ ]
347
+
348
+ # Copy from GPU paged buffer to tmp buffer, then to CPU — per group
349
+ for group_idx in range(num_groups):
350
+ tmp_buffer = gpu_context.get_tmp_chunk_gpu_buffer(group_idx)
351
+ group_kv_pointers = gpu_context.get_group_kv_pointers(group_idx)
352
+ lmc_ops.multi_layer_block_kv_transfer(
353
+ group_kv_pointers,
354
+ [tmp_buffer.data_ptr()],
355
+ chunk_block_ids_gpu,
356
+ gpu_context.device,
357
+ lmc_ops.TransferDirection.D2H,
358
+ gpu_context.get_shape_desc(group_idx),
359
+ self.chunk_size,
360
+ gpu_context.gpu_kv_format_,
361
+ 0,
362
+ )
363
+ # Store is not batched, so we always use chunk_idx=0 (single slot)
364
+ lmcache_memcpy_async_d2h(
365
+ gpu_context.get_tmp_gpu_buffer_flat(chunk_idx=0), memory_obj
366
+ )
367
+ except Exception:
368
+ logger.exception("Cannot store keys due to exception")
369
+ finally:
370
+ event.record()
371
+ if reserved_dict:
372
+ gpu_context.cupy_stream.launch_host_func(
373
+ self.storage_manager.finish_write,
374
+ list(reserved_dict.keys()),
375
+ )
376
+ self._event_bus.publish_on_stream(
377
+ gpu_context.cupy_stream,
378
+ Event(
379
+ event_type=EventType.MP_STORE_END,
380
+ session_id=key.request_id,
381
+ metadata={
382
+ "stored_count": len(reserved_dict),
383
+ "device": str(gpu_context.device),
384
+ },
385
+ ),
386
+ )
387
+
388
+ ed = time.perf_counter()
389
+ if length := len(reserved_dict):
390
+ logger.info(
391
+ "Stored %d tokens in %.3f seconds",
392
+ length * self.chunk_size,
393
+ ed - st,
394
+ )
395
+ return event.ipc_handle(), True
396
+
397
+ @_lmcache_nvtx_annotate
398
+ def retrieve(
399
+ self,
400
+ key: IPCCacheEngineKey,
401
+ instance_id: int,
402
+ gpu_block_ids: list[int],
403
+ event_ipc_handle: bytes,
404
+ skip_first_n_tokens: int = 0,
405
+ ) -> tuple[bytes, bool]:
406
+ """
407
+ Retrieves the CPU KV cache and put into GPU blocks.
408
+
409
+ Args:
410
+ key (IPCCacheEngineKey): The IPC key for the KV cache blocks.
411
+ Must have worker_id != None (worker retrieve operation).
412
+ instance_id (int): The GPU instance ID (such as PID).
413
+ gpu_block_ids (list[int]): The GPU block IDs to retrieve into.
414
+ event_ipc_handle (bytes): The IPC handle of the event to wait on.
415
+ skip_first_n_tokens (int): Number of tokens to skip writing at
416
+ the start of the retrieve range. This avoids overwriting
417
+ APC-shared GPU blocks that may be read concurrently by other
418
+ requests.
419
+
420
+ Returns:
421
+ tuple[bytes, bool]: The first element is the IPC handle of the event
422
+ that signals the completion of the retrieve operation. The second
423
+ element indicates whether the key was successfully retrieved.
424
+ """
425
+ session = self.session_manager.get_or_create(key.request_id)
426
+ session.set_tokens(list(key.token_ids))
427
+ chunk_hashes = [
428
+ TokenHasher.hash_to_bytes(h) for h in session.get_hashes(key.start, key.end)
429
+ ]
430
+
431
+ st = time.perf_counter()
432
+
433
+ assert key.worker_id is not None, "Must retrieve with worker_id != None"
434
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
435
+
436
+ assert instance_id in self.gpu_contexts, (
437
+ f"KV cache not registered for GPU ID {instance_id}"
438
+ )
439
+ gpu_context = self.gpu_contexts[instance_id]
440
+
441
+ # CPU-synchronous sentinel: a GPU retrieve is about to be enqueued.
442
+ # Must be published via publish() (not publish_on_stream) so the
443
+ # drain thread sees it before MP_SESSION_END can race MP_RETRIEVE_END.
444
+ self._event_bus.publish(
445
+ Event(
446
+ event_type=EventType.MP_RETRIEVE_SUBMITTED,
447
+ session_id=key.request_id,
448
+ metadata={"device": str(gpu_context.device)},
449
+ )
450
+ )
451
+
452
+ self._event_bus.publish_on_stream(
453
+ gpu_context.cupy_stream,
454
+ Event(
455
+ event_type=EventType.MP_RETRIEVE_START,
456
+ session_id=key.request_id,
457
+ metadata={"device": str(gpu_context.device)},
458
+ ),
459
+ )
460
+
461
+ blocks_per_chunk = self.chunk_size // gpu_context.block_size
462
+
463
+ def _retrieve_loop(keys: list[ObjectKey], memory_objs: list[MemoryObj]) -> None:
464
+ _BATCH_SIZE = gpu_context.max_batch_size
465
+ num_groups = gpu_context.kv_layer_groups_manager.num_groups
466
+ for batch_idx, memory_obj_batch in enumerate(
467
+ batched_iteration(memory_objs, batch_size=_BATCH_SIZE)
468
+ ):
469
+ batch_len = len(memory_obj_batch)
470
+ chunk_start = batch_idx * self.chunk_size * _BATCH_SIZE
471
+ chunk_end = chunk_start + self.chunk_size * batch_len
472
+
473
+ effective_start = max(chunk_start, skip_first_n_tokens)
474
+ if effective_start >= chunk_end:
475
+ # Entire batch is within APC range, skip it
476
+ continue
477
+
478
+ skip_tokens_in_chunk = max(
479
+ 0,
480
+ min(
481
+ effective_start - chunk_start,
482
+ self.chunk_size * batch_len - 1,
483
+ ),
484
+ )
485
+ if skip_tokens_in_chunk % gpu_context.block_size != 0:
486
+ logger.error(
487
+ "skip_first_n_tokens (%d) is not aligned to block_size (%d), "
488
+ "rounding down from %d tokens to %d blocks",
489
+ skip_first_n_tokens,
490
+ gpu_context.block_size,
491
+ skip_tokens_in_chunk,
492
+ skip_tokens_in_chunk // gpu_context.block_size,
493
+ )
494
+ skip_blocks_in_chunk = skip_tokens_in_chunk // gpu_context.block_size
495
+
496
+ start_chunk_id = batch_idx * _BATCH_SIZE
497
+ end_chunk_id = start_chunk_id + batch_len
498
+ chunk_block_ids_gpu = all_block_ids_gpu[
499
+ start_chunk_id * blocks_per_chunk : end_chunk_id * blocks_per_chunk
500
+ ]
501
+
502
+ # Copy from CPU to GPU tmp buffers, then scatter to paged KV — per group
503
+ # H2D copy: each memory_obj maps to its own batch slot
504
+ for chunk_idx, memory_obj in enumerate(memory_obj_batch):
505
+ lmcache_memcpy_async_h2d(
506
+ memory_obj,
507
+ gpu_context.get_tmp_gpu_buffer_flat(chunk_idx=chunk_idx),
508
+ )
509
+ for group_idx in range(num_groups):
510
+ tmp_buffers = gpu_context.get_tmp_chunk_gpu_buffer_batched(
511
+ batch_len, group_idx
512
+ )
513
+ group_kv_pointers = gpu_context.get_group_kv_pointers(group_idx)
514
+
515
+ lmc_ops.multi_layer_block_kv_transfer(
516
+ group_kv_pointers,
517
+ [tb.data_ptr() for tb in tmp_buffers],
518
+ chunk_block_ids_gpu,
519
+ gpu_context.device,
520
+ lmc_ops.TransferDirection.H2D,
521
+ gpu_context.get_shape_desc(group_idx),
522
+ self.chunk_size,
523
+ gpu_context.gpu_kv_format_,
524
+ skip_blocks_in_chunk,
525
+ )
526
+
527
+ with (
528
+ torch.cuda.device(gpu_context.device),
529
+ torch.cuda.stream(gpu_context.stream),
530
+ ):
531
+ # Stage all block_ids to GPU once before the loop
532
+ all_block_ids_gpu = gpu_context.stage_block_ids(gpu_block_ids)
533
+
534
+ event = torch.cuda.Event(interprocess=True)
535
+
536
+ prefetched_keys: list[ObjectKey] = []
537
+ retrieve_succeeded = False
538
+ try:
539
+ with self.storage_manager.read_prefetched_results(
540
+ obj_keys
541
+ ) as memory_objs:
542
+ if not memory_objs or len(memory_objs) != len(obj_keys):
543
+ logger.error("Some keys not found during retrieve!")
544
+ return event.ipc_handle(), False
545
+
546
+ prefetched_keys = obj_keys[: len(memory_objs)]
547
+ _retrieve_loop(obj_keys, memory_objs)
548
+ # Only set True when with-block exits normally
549
+ retrieve_succeeded = True
550
+ except Exception:
551
+ logger.exception("Cannot retrieve keys due to exception")
552
+ return event.ipc_handle(), False
553
+ finally:
554
+ event.record()
555
+ if retrieve_succeeded:
556
+ gpu_context.cupy_stream.launch_host_func(
557
+ self.storage_manager.finish_read_prefetched,
558
+ prefetched_keys,
559
+ )
560
+ self._event_bus.publish_on_stream(
561
+ gpu_context.cupy_stream,
562
+ Event(
563
+ event_type=EventType.MP_RETRIEVE_END,
564
+ session_id=key.request_id,
565
+ metadata={
566
+ "retrieved_count": len(prefetched_keys),
567
+ "device": str(gpu_context.device),
568
+ },
569
+ ),
570
+ )
571
+ tokens_retrieved = len(obj_keys) * self.chunk_size
572
+ ed = time.perf_counter()
573
+ logger.info(
574
+ "Retrieved %d tokens in %.3f seconds",
575
+ tokens_retrieved,
576
+ ed - st,
577
+ )
578
+
579
+ return event.ipc_handle(), True
580
+
581
+ def _find_layout_desc(
582
+ self,
583
+ model_name: str,
584
+ world_size: int,
585
+ ) -> MemoryLayoutDesc | None:
586
+ """Find layout desc from a matching GPU context.
587
+
588
+ Returns:
589
+ The layout descriptor, or None if no context
590
+ matches (model_name, world_size).
591
+ """
592
+ for gpu_id, (m, w) in self.gpu_context_meta.items():
593
+ if m == model_name and w == world_size:
594
+ return get_layout_desc(
595
+ self.gpu_contexts[gpu_id],
596
+ self.chunk_size,
597
+ )
598
+ return None
599
+
600
+ def lookup(
601
+ self,
602
+ key: IPCCacheEngineKey,
603
+ tp_size: int,
604
+ ) -> None:
605
+ """Submit a prefix lookup.
606
+
607
+ Hashes the key, submits a prefetch task to the storage manager,
608
+ and registers the job under ``key.request_id`` for later polling
609
+ via query_prefetch_status.
610
+
611
+ Args:
612
+ key: Cache key with request_id embedded.
613
+ tp_size: Tensor-parallel size for MLA multi-reader locking.
614
+ """
615
+ model_name, world_size = key.model_name, key.world_size
616
+ self._event_bus.publish(
617
+ Event(
618
+ event_type=EventType.MP_REQUEST_START,
619
+ session_id=key.request_id,
620
+ )
621
+ )
622
+ self._event_bus.publish(
623
+ Event(
624
+ event_type=EventType.MP_LOOKUP_PREFETCH_START,
625
+ session_id=key.request_id,
626
+ )
627
+ )
628
+
629
+ layout_desc = self._find_layout_desc(model_name, world_size)
630
+ if layout_desc is None:
631
+ logger.error(
632
+ "No GPU context found for model %s with world size %d during lookup!",
633
+ model_name,
634
+ world_size,
635
+ )
636
+ self._register_prefetch_job(
637
+ _PrefetchJob(
638
+ handle=PrefetchHandle(
639
+ prefetch_request_id=-1,
640
+ external_request_id=key.request_id,
641
+ l1_prefix_hit_count=0,
642
+ total_requested_keys=0,
643
+ submit_time=time.monotonic(),
644
+ ),
645
+ world_size=1,
646
+ request_id=key.request_id,
647
+ )
648
+ )
649
+ return
650
+
651
+ extra_count = compute_extra_count(tp_size, world_size)
652
+
653
+ # Compute chunk hashes for all full chunks
654
+ chunk_hashes = self.token_hasher.compute_chunk_hashes(list(key.token_ids))
655
+ if not chunk_hashes:
656
+ self._register_prefetch_job(
657
+ _PrefetchJob(
658
+ handle=PrefetchHandle(
659
+ prefetch_request_id=-1,
660
+ external_request_id=key.request_id,
661
+ l1_prefix_hit_count=0,
662
+ total_requested_keys=0,
663
+ submit_time=time.monotonic(),
664
+ ),
665
+ world_size=1,
666
+ request_id=key.request_id,
667
+ )
668
+ )
669
+ return
670
+
671
+ # Publish lookup event via EventBus for observability subscribers.
672
+ # Guard with has_subscribers() to avoid allocating the metadata dict
673
+ # (including dtype/shape list comprehensions) when no subscriber is
674
+ # listening (e.g. lookup hash logger is disabled).
675
+ if self._event_bus.has_subscribers(EventType.MP_LOOKUP):
676
+ self._event_bus.publish(
677
+ Event(
678
+ event_type=EventType.MP_LOOKUP,
679
+ session_id=key.request_id,
680
+ metadata={
681
+ "request_id": key.request_id,
682
+ "chunk_hashes": chunk_hashes,
683
+ "model_name": model_name,
684
+ "chunk_size": self.chunk_size,
685
+ "seq_len": len(key.token_ids),
686
+ "dtypes": [str(d) for d in layout_desc.dtypes],
687
+ "shapes": [list(s) for s in layout_desc.shapes],
688
+ },
689
+ )
690
+ )
691
+
692
+ # set lookup ipc key, for session manager to use and generate object keys
693
+ session = self.session_manager.get_or_create(key.request_id)
694
+ session.set_tokens(list(key.token_ids))
695
+ session.lookup_ipc_key = key
696
+
697
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
698
+
699
+ handle = self.storage_manager.submit_prefetch_task(
700
+ obj_keys,
701
+ layout_desc,
702
+ extra_count=extra_count,
703
+ external_request_id=key.request_id,
704
+ )
705
+ self._register_prefetch_job(
706
+ _PrefetchJob(
707
+ handle=handle,
708
+ world_size=key.world_size,
709
+ request_id=key.request_id,
710
+ )
711
+ )
712
+
713
+ def _register_prefetch_job(self, job: _PrefetchJob) -> None:
714
+ with self._prefetch_job_lock:
715
+ self._prefetch_jobs[job.request_id] = job
716
+
717
+ def query_prefetch_lookup_hits(
718
+ self,
719
+ request_id: str,
720
+ ) -> int | None:
721
+ """Query the number of hits for a prefetch request before it's finished.
722
+
723
+ Returns:
724
+ The number of hits for the prefetched keys if the lookup phase is
725
+ done. None if the lookup phase is still in progress. 0 if the
726
+ request_id is unknown (already completed and consumed, or invalid).
727
+ """
728
+ with self._prefetch_job_lock:
729
+ job = self._prefetch_jobs.get(request_id)
730
+
731
+ if job is None:
732
+ logger.warning(
733
+ "Prefetch job for request %s not found (already completed or invalid)",
734
+ request_id,
735
+ )
736
+ return 0
737
+
738
+ found_count = self.storage_manager.query_prefetch_lookup_hits(job.handle)
739
+ if found_count is None:
740
+ return None
741
+
742
+ found_count = found_count // job.world_size
743
+ return found_count
744
+
745
+ def query_prefetch_status(
746
+ self,
747
+ request_id: str,
748
+ ) -> int | None:
749
+ """Poll the status of a prefetch job by request_id.
750
+
751
+ Returns the chunk count when the prefetch is complete, or None
752
+ if it is still in progress. The job entry is automatically
753
+ removed once a non-None result is returned (exactly-once
754
+ semantics).
755
+
756
+ Args:
757
+ request_id: The external request ID passed in the lookup key.
758
+
759
+ Returns:
760
+ Chunk count (int) when done, None if still in progress,
761
+ 0 if the request_id is unknown (already completed and consumed,
762
+ or invalid).
763
+ """
764
+ with self._prefetch_job_lock:
765
+ job = self._prefetch_jobs.get(request_id)
766
+ if job is None:
767
+ logger.warning(
768
+ "Prefetch job for request %s not found (already completed or invalid)",
769
+ request_id,
770
+ )
771
+ return 0
772
+
773
+ found_count = self.storage_manager.query_prefetch_status(job.handle)
774
+ if found_count is None:
775
+ return None
776
+
777
+ # NOTE(Kuntai): this assumes two things:
778
+ # 1. the world size is the same between keys
779
+ # 2. the lookup sort the keys in prefix order and breaks at the
780
+ # first failure
781
+ found_count = found_count // job.world_size
782
+
783
+ self._event_bus.publish(
784
+ Event(
785
+ event_type=EventType.MP_LOOKUP_PREFETCH_END,
786
+ session_id=job.request_id,
787
+ metadata={"found_count": found_count},
788
+ )
789
+ )
790
+
791
+ with self._prefetch_job_lock:
792
+ self._prefetch_jobs.pop(request_id, None)
793
+
794
+ return found_count
795
+
796
+ def free_lookup_locks(
797
+ self,
798
+ key: IPCCacheEngineKey,
799
+ tp_size: int,
800
+ ) -> None:
801
+ """Release read locks acquired during lookup.
802
+
803
+ Hashes are computed only for chunks in ``[start, end)`` to avoid
804
+ unnecessary work on tokens outside that range.
805
+ ``start`` and ``end`` must be aligned to ``chunk_size``; it is the
806
+ caller's responsibility to align the boundaries as desired.
807
+
808
+ Computes the extra reader count from ``tp_size`` and
809
+ ``world_size`` the same way :meth:`lookup` does, so
810
+ the correct number of locks is released.
811
+
812
+ Args:
813
+ key: Cache key whose read locks should be released.
814
+ tp_size: Tensor-parallel size for MLA
815
+ multi-reader locking.
816
+ """
817
+ chunk_hashes = self.token_hasher.compute_chunk_hashes(
818
+ list(key.token_ids), start=key.start, end=key.end
819
+ )
820
+ if not chunk_hashes:
821
+ return
822
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
823
+
824
+ extra_count = compute_extra_count(tp_size, key.world_size)
825
+
826
+ self.storage_manager.finish_read_prefetched(obj_keys, extra_count=extra_count)
827
+
828
+ # =========================================================================
829
+ # Utility methods
830
+ # =========================================================================
831
+
832
+ def ping(self) -> bool:
833
+ """
834
+ Respond to a ping request.
835
+
836
+ Returns:
837
+ bool: Always True.
838
+ """
839
+ return True
840
+
841
+ def get_chunk_size(self) -> int:
842
+ """
843
+ Returns the chunk size used for KV cache operations.
844
+
845
+ Returns:
846
+ int: The chunk size.
847
+ """
848
+ return self.chunk_size
849
+
850
+ def end_session(self, request_id: str) -> None:
851
+ """Remove the session for a finished request.
852
+
853
+ Args:
854
+ request_id: The request ID whose session should be removed.
855
+ """
856
+ self._event_bus.publish(
857
+ Event(
858
+ event_type=EventType.MP_VLLM_END_SESSION,
859
+ metadata={"request_id": request_id},
860
+ )
861
+ )
862
+ session = self.session_manager.remove(request_id)
863
+ self._event_bus.publish(
864
+ Event(
865
+ event_type=EventType.MP_SESSION_END,
866
+ session_id=request_id,
867
+ )
868
+ )
869
+ if session is None:
870
+ logger.warning("Session %s not found, skipping touch", request_id)
871
+ return
872
+ if session.lookup_ipc_key is None:
873
+ logger.warning(
874
+ "Session %s has no lookup ipc key, skipping touch", request_id
875
+ )
876
+ return
877
+
878
+ chunk_hashes = [TokenHasher.hash_to_bytes(h) for h in session.get_hashes(0)]
879
+ obj_keys = ipc_key_to_object_keys(session.lookup_ipc_key, chunk_hashes)
880
+ # unified touch of all keys, which include retrieved and stored keys
881
+ # TODO(chunxiaozheng): when l2 is enabled, the prefetched keys from l2 are temp
882
+ # and will be deleted after finish_read_prefetched, when we touch all keys,
883
+ # these keys has been deleted and will not be touched.
884
+ self.storage_manager.touch_l1_keys(obj_keys)
885
+
886
+ def report_status(self) -> dict:
887
+ """Return a status dict for the entire cache engine."""
888
+ sm = self.storage_manager.report_status()
889
+
890
+ gpu_context_meta: dict[str, dict] = {}
891
+ for gpu_id, meta in self.gpu_context_meta.items():
892
+ entry: dict = {
893
+ "model_name": meta[0],
894
+ "world_size": meta[1],
895
+ }
896
+ ctx = self.gpu_contexts.get(gpu_id)
897
+ if ctx is not None:
898
+ entry["kv_cache_layout"] = {
899
+ "num_layers": ctx.num_layers,
900
+ "block_size": ctx.block_size,
901
+ "hidden_dim_sizes": str(ctx.hidden_dim_sizes),
902
+ "dtype": str(ctx.dtype),
903
+ "is_mla": ctx.is_mla,
904
+ "num_blocks": ctx.num_blocks,
905
+ "gpu_kv_format": ctx.gpu_kv_format_name,
906
+ "gpu_kv_shape": ctx.gpu_kv_shape,
907
+ "gpu_kv_concrete_shape": ctx.concrete_gpu_kv_shape,
908
+ "attention_backend": ctx.attention_backend,
909
+ "cache_size_per_token": ctx.cache_size_per_token(),
910
+ }
911
+ gpu_context_meta[str(gpu_id)] = entry
912
+
913
+ return {
914
+ "is_healthy": sm["is_healthy"],
915
+ "engine_type": self.__class__.__name__,
916
+ "chunk_size": self.chunk_size,
917
+ "hash_algorithm": self.token_hasher.hash_algorithm_name,
918
+ "registered_gpu_ids": list(self.gpu_contexts.keys()),
919
+ "gpu_context_meta": gpu_context_meta,
920
+ "active_sessions": self.session_manager.active_count(),
921
+ "active_prefetch_jobs": self._active_prefetch_count(),
922
+ "storage_manager": sm,
923
+ }
924
+
925
+ def report_block_allocations(
926
+ self,
927
+ instance_id: int,
928
+ model_name: str,
929
+ records: list[BlockAllocationRecord],
930
+ ) -> None:
931
+ """Publish vLLM block allocation records to the EventBus.
932
+
933
+ Args:
934
+ instance_id: The scheduler instance ID.
935
+ model_name: The model name from the adapter.
936
+ records: List of BlockAllocationRecord with per-request
937
+ block and token allocation deltas.
938
+ """
939
+ self._event_bus.publish(
940
+ Event(
941
+ event_type=EventType.MP_VLLM_BLOCK_ALLOCATION,
942
+ metadata={
943
+ "instance_id": instance_id,
944
+ "model_name": model_name,
945
+ "records": records,
946
+ },
947
+ )
948
+ )
949
+
950
+ def debug(self) -> str:
951
+ return "OK"
952
+
953
+ def clear(self) -> None:
954
+ """
955
+ Clears all stored KV cache data from the storage manager.
956
+ """
957
+ with self.lock:
958
+ self.storage_manager.memcheck()
959
+ self.storage_manager.clear(force=True)
960
+ self.storage_manager.memcheck()
961
+
962
+ def close(self) -> None:
963
+ """
964
+ Closes the MPCacheEngine and releases all resources.
965
+ """
966
+ # Close storage manager
967
+ self.storage_manager.close()
968
+ logger.info("MPCacheEngine closed")
969
+
970
+ # Release GPU contexts
971
+ self.gpu_contexts.clear()
972
+
973
+ def _active_prefetch_count(self) -> int:
974
+ """Return the number of active prefetch jobs (thread-safe)."""
975
+ with self._prefetch_job_lock:
976
+ return len(self._prefetch_jobs)
977
+
978
+ def _setup_metrics(self) -> None:
979
+ """Register OTel observable gauges for MP engine metrics."""
980
+ _gauge = partial(register_gauge, "lmcache.mp_engine")
981
+ _gauge(
982
+ "lmcache_mp.active_prefetch_jobs",
983
+ "Number of active prefetch jobs",
984
+ self._active_prefetch_count,
985
+ )
986
+
987
+
988
+ def add_handler_helper(
989
+ server: MessageQueueServer, request_type: RequestType, handler_function
990
+ ):
991
+ payload_classes = get_payload_classes(request_type)
992
+ handler_type = get_handler_type(request_type)
993
+ server.add_handler(
994
+ request_type,
995
+ payload_classes,
996
+ handler_type,
997
+ handler_function,
998
+ )
999
+
1000
+
1001
+ def run_cache_server(
1002
+ mp_config: MPServerConfig,
1003
+ storage_manager_config: StorageManagerConfig,
1004
+ obs_config: ObservabilityConfig,
1005
+ return_engine: bool = False,
1006
+ ):
1007
+ """
1008
+ Run the LMCache cache server with ZMQ message queue.
1009
+
1010
+ Args:
1011
+ mp_config: Configuration for the ZMQ multiprocess server
1012
+ storage_manager_config: Configuration for the storage manager
1013
+ obs_config: Configuration for the observability stack
1014
+ return_engine: If True, return (server, engine) after starting;
1015
+ if False, run blocking loop to keep server alive
1016
+
1017
+ Returns:
1018
+ If return_engine is True: tuple of (MessageQueueServer, MPCacheEngine)
1019
+ If return_engine is False: None (blocks until interrupted)
1020
+ """
1021
+ event_bus = init_observability(obs_config)
1022
+
1023
+ # Wire up the trace recorder (no-op when --trace-level is unset).
1024
+ # Registered before the engine handlers are added so any
1025
+ # storage-manager calls during engine init are captured too.
1026
+ maybe_initialize_trace_recorder(event_bus, obs_config, storage_manager_config)
1027
+
1028
+ # Initialize the engine (loggers self-register with the global controller)
1029
+ engine = MPCacheEngine(
1030
+ storage_manager_config=storage_manager_config,
1031
+ chunk_size=mp_config.chunk_size,
1032
+ hash_algorithm=mp_config.hash_algorithm,
1033
+ )
1034
+
1035
+ # Initialize the message queue server
1036
+ context = zmq.Context.instance()
1037
+ server = MessageQueueServer(
1038
+ bind_url=f"tcp://{mp_config.host}:{mp_config.port}",
1039
+ context=context,
1040
+ )
1041
+
1042
+ # Add handlers
1043
+ add_handler_helper(server, RequestType.REGISTER_KV_CACHE, engine.register_kv_cache)
1044
+ add_handler_helper(
1045
+ server, RequestType.UNREGISTER_KV_CACHE, engine.unregister_kv_cache
1046
+ )
1047
+ add_handler_helper(server, RequestType.STORE, engine.store)
1048
+ add_handler_helper(server, RequestType.LOOKUP, engine.lookup)
1049
+ add_handler_helper(
1050
+ server, RequestType.QUERY_PREFETCH_STATUS, engine.query_prefetch_status
1051
+ )
1052
+ add_handler_helper(
1053
+ server,
1054
+ RequestType.QUERY_PREFETCH_LOOKUP_HITS,
1055
+ engine.query_prefetch_lookup_hits,
1056
+ )
1057
+ add_handler_helper(server, RequestType.FREE_LOOKUP_LOCKS, engine.free_lookup_locks)
1058
+ add_handler_helper(server, RequestType.RETRIEVE, engine.retrieve)
1059
+ add_handler_helper(server, RequestType.CLEAR, engine.clear)
1060
+ add_handler_helper(server, RequestType.GET_CHUNK_SIZE, engine.get_chunk_size)
1061
+ add_handler_helper(server, RequestType.PING, engine.ping)
1062
+ add_handler_helper(server, RequestType.END_SESSION, engine.end_session)
1063
+ add_handler_helper(server, RequestType.NOOP, engine.debug)
1064
+ add_handler_helper(
1065
+ server,
1066
+ RequestType.REPORT_BLOCK_ALLOCATION,
1067
+ engine.report_block_allocations,
1068
+ )
1069
+
1070
+ # Assign thread pools
1071
+ server.add_affinity_thread_pool(
1072
+ [RequestType.STORE, RequestType.RETRIEVE],
1073
+ max_workers=mp_config.max_gpu_workers,
1074
+ )
1075
+ server.add_normal_thread_pool(
1076
+ [
1077
+ RequestType.LOOKUP,
1078
+ RequestType.QUERY_PREFETCH_STATUS,
1079
+ RequestType.QUERY_PREFETCH_LOOKUP_HITS,
1080
+ RequestType.FREE_LOOKUP_LOCKS,
1081
+ RequestType.END_SESSION,
1082
+ RequestType.CLEAR,
1083
+ RequestType.PING,
1084
+ RequestType.REPORT_BLOCK_ALLOCATION,
1085
+ ],
1086
+ max_workers=mp_config.max_cpu_workers,
1087
+ )
1088
+
1089
+ logger.info(
1090
+ "LMCache ZMQ cache server is running on tcp://%s:%d",
1091
+ mp_config.host,
1092
+ mp_config.port,
1093
+ )
1094
+ # Start the ZMQ server
1095
+ torch.cuda.init()
1096
+ server.start()
1097
+
1098
+ logger.info("LMCache cache server is running...")
1099
+
1100
+ # Return server and engine if requested (for HTTP server integration)
1101
+ if return_engine:
1102
+ return server, engine
1103
+
1104
+ # Dummy loop to keep the server running
1105
+ try:
1106
+ while True:
1107
+ time.sleep(1)
1108
+ except KeyboardInterrupt:
1109
+ logger.info("Shutting down server...")
1110
+ event_bus.stop()
1111
+ server.close()
1112
+ engine.close()
1113
+
1114
+
1115
+ def parse_args():
1116
+ parser = argparse.ArgumentParser(
1117
+ description="LMCache ZMQ Cache Server (without HTTP)"
1118
+ )
1119
+ add_mp_server_args(parser)
1120
+ add_storage_manager_args(parser)
1121
+ add_observability_args(parser)
1122
+ return parser.parse_args()
1123
+
1124
+
1125
+ if __name__ == "__main__":
1126
+ args = parse_args()
1127
+ mp_config = parse_args_to_mp_server_config(args)
1128
+ storage_manager_config = parse_args_to_config(args)
1129
+ obs_config = parse_args_to_observability_config(args)
1130
+ run_cache_server(
1131
+ mp_config=mp_config,
1132
+ storage_manager_config=storage_manager_config,
1133
+ obs_config=obs_config,
1134
+ )