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,891 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Overview
4
+ --------
5
+ This server enables KV cache reuse across requests that share token
6
+ sub-sequences at *arbitrary positions*, not only at a common prefix.
7
+
8
+ Workflow (example: chunk_size = 3)
9
+ -----------------------------------
10
+ 1. cb_store_pre_computed([1,2,3,4,5,6])
11
+ Tokens are split into full chunks ([1,2,3] and [4,5,6]). Each chunk
12
+ is stored in the underlying storage under its normal rolling prefix
13
+ hash, and the chunk fingerprints are registered in
14
+ BlendTokenRangeMatcher for fast sub-sequence lookup. Because normal
15
+ hashes are used, these chunks are also accessible via the standard
16
+ lookup/retrieve path.
17
+
18
+ 2. cb_lookup_pre_computed([x,y,z, a,b,c, 4,5,6, m,n,p])
19
+ BlendTokenRangeMatcher slides a rolling polynomial hash over the new
20
+ request's tokens and detects that the window at positions [6, 9)
21
+ matches the stored chunk [4,5,6]. A prefetch task is submitted for
22
+ that chunk using its stored hash as the storage key. Only chunks
23
+ confirmed present in storage are returned as CBMatchResult objects
24
+ (with cur_st/cur_ed pointing to their location in the new request).
25
+
26
+ 3. cb_retrieve_pre_computed(...)
27
+ The (prefetched) KV cache for each matched chunk is copied (CPU→GPU)
28
+ into the correct slot of the new request's KV cache buffer (at
29
+ cur_st + offset), so the LLM can skip recomputing those tokens.
30
+
31
+ 4. cb_store_final([x,y,z, a,b,c, 4,5,6, m,n,p])
32
+ After inference completes on the new request, all its chunks are
33
+ stored under normal prefix hashes. Future requests sharing
34
+ any prefix of the new request will get standard prefix-cache hits.
35
+ Future requests sharing any prefix of the first request will also
36
+ get hits because cb_store_pre_computed already stored those chunks
37
+ under normal hashes.
38
+ """
39
+
40
+ # Standard
41
+ import time
42
+
43
+ # Third Party
44
+ import numpy as np
45
+ import torch
46
+ import zmq
47
+
48
+ # First Party
49
+ from lmcache.logging import init_logger
50
+ from lmcache.v1.distributed.api import (
51
+ MemoryLayoutDesc,
52
+ ObjectKey,
53
+ ipc_key_to_object_keys,
54
+ )
55
+ from lmcache.v1.distributed.config import (
56
+ StorageManagerConfig,
57
+ parse_args_to_config,
58
+ )
59
+ from lmcache.v1.distributed.storage_manager import PrefetchHandle
60
+ from lmcache.v1.gpu_connector.gpu_ops import (
61
+ lmcache_memcpy_async_d2h,
62
+ lmcache_memcpy_async_h2d,
63
+ )
64
+ from lmcache.v1.mp_observability.config import (
65
+ ObservabilityConfig,
66
+ init_observability,
67
+ parse_args_to_observability_config,
68
+ )
69
+ from lmcache.v1.mp_observability.trace import maybe_initialize_trace_recorder
70
+ from lmcache.v1.multiprocess.config import (
71
+ MPServerConfig,
72
+ parse_args_to_mp_server_config,
73
+ )
74
+ from lmcache.v1.multiprocess.custom_types import (
75
+ CBMatchResult,
76
+ IPCCacheEngineKey,
77
+ KVCache,
78
+ )
79
+ from lmcache.v1.multiprocess.gpu_context import (
80
+ PlainGPUCacheContext,
81
+ )
82
+ from lmcache.v1.multiprocess.mq import MessageQueueServer
83
+ from lmcache.v1.multiprocess.protocol import (
84
+ RequestType,
85
+ get_handler_type,
86
+ get_payload_classes,
87
+ )
88
+ from lmcache.v1.multiprocess.server import MPCacheEngine, parse_args
89
+ from lmcache.v1.multiprocess.token_hasher import (
90
+ chunk_hash_windows_numba,
91
+ rolling_hash_windows_numba,
92
+ unique_hits_direct_id_numba,
93
+ update_table_id_numba,
94
+ )
95
+
96
+ logger = init_logger(__name__)
97
+
98
+
99
+ class BlendTokenRangeMatcher:
100
+ # TODO(Jiayi): Needs thread-safety for this class.
101
+ """Fast token-range matcher using polynomial rolling/chunk hashes and a
102
+ direct-address lookup table.
103
+
104
+ Table layout: poly_chunk_hash (u64) → compact_chunk_id (i64, sequential 0…N-1).
105
+
106
+ Because compact IDs are bounded by _TABLE_SIZE, unique_hits_direct_id_numba
107
+ can use a fixed `seen` array of _TABLE_SIZE bytes (~1 MB) rather than one
108
+ sized by an arbitrary max hash — no memory explosion.
109
+
110
+ Auxiliary storage:
111
+ _chunk_token_hash[i] : token_hash for chunk i (None if evicted)
112
+ _token_hash_to_start : token_hash → start position in seq
113
+ _compact_id_to_slot[i] : table slot for compact_id i
114
+ _token_hash_to_compact_id : token_hash → compact_chunk_id
115
+
116
+ Methods:
117
+ on_new_token_hashes – register a sequence; builds fingerprints
118
+ and writes compact IDs.
119
+ match_sub_sequence – sliding-window probe → compact IDs →
120
+ token_hash → start. Skips evicted entries.
121
+ remove_chunks – lazily evict stale entries. Clears the
122
+ table slot and auxiliary maps.
123
+ """
124
+
125
+ _TABLE_BITS: int = 20 # 2^20 ≈ 1 M entries
126
+ _TABLE_SIZE: int = 1 << _TABLE_BITS
127
+ _BASE: np.uint64 = np.uint64(0x9E3779B97F4A7C15) # Fibonacci-hashing constant
128
+
129
+ def __init__(self, chunk_size: int = 256):
130
+ self.chunk_size = chunk_size
131
+ # poly_chunk_hash → compact_chunk_id; -1 = empty
132
+ self._table_id = np.full(self._TABLE_SIZE, -1, dtype=np.int64)
133
+ self._mask = np.uint64(self._TABLE_SIZE - 1)
134
+ # compact_chunk_id → caller-supplied token_hash (full bytes)
135
+ self._chunk_token_hash: list[bytes | None] = []
136
+ # token_hash → start position in its registered sequence
137
+ self._token_hash_to_start: dict[bytes, int] = {}
138
+ # compact_chunk_id → table slot index (for reverse lookup during eviction)
139
+ self._compact_id_to_slot = np.full(self._TABLE_SIZE, -1, dtype=np.int64)
140
+ # token_hash → compact_chunk_id (for eviction lookup)
141
+ self._token_hash_to_compact_id: dict[bytes, int] = {}
142
+
143
+ def on_new_token_hashes(
144
+ self,
145
+ token_ids: list[int],
146
+ token_hashes: list[bytes],
147
+ ):
148
+ """Register a new token sequence and index its non-overlapping chunks.
149
+
150
+ Args:
151
+ token_ids: Raw token IDs for the full sequence (num_tokens elements).
152
+ Used to compute polynomial chunk fingerprints that match
153
+ the rolling hashes computed in match_sub_sequence.
154
+ token_hashes: Per-chunk bytes hashes supplied by the caller
155
+ (one per complete chunk of chunk_size tokens).
156
+ Stored as the storage key returned in CBMatchResult.hash.
157
+ """
158
+ arr = np.array(token_ids, dtype=np.uint64)
159
+ # Polynomial fingerprints for non-overlapping chunks, built from raw token IDs
160
+ # so they match the sliding-window rolling hashes in match_sub_sequence
161
+ chunk_hashes = chunk_hash_windows_numba(arr, self.chunk_size, self._BASE)
162
+ n = int(chunk_hashes.shape[0])
163
+ if n == 0:
164
+ return
165
+
166
+ # Compact sequential IDs: bounded by _TABLE_SIZE, safe for seen-array sizing
167
+ base_id = len(self._chunk_token_hash)
168
+ compact_ids = np.arange(base_id, base_id + n, dtype=np.int64)
169
+
170
+ # Write table: poly_chunk_hash → compact_chunk_id
171
+ update_table_id_numba(chunk_hashes, self._table_id, compact_ids)
172
+
173
+ # Persist compact_id → token_hash, token_hash → start, and reverse maps
174
+ for i in range(n):
175
+ th = token_hashes[i]
176
+ cid = int(compact_ids[i])
177
+ slot = int(chunk_hashes[i]) & int(self._mask)
178
+ self._chunk_token_hash.append(th)
179
+ self._token_hash_to_start[th] = i * self.chunk_size
180
+ self._compact_id_to_slot[cid] = slot
181
+ self._token_hash_to_compact_id[th] = cid
182
+
183
+ def match_sub_sequence(
184
+ self,
185
+ token_ids: list[int],
186
+ ) -> list[CBMatchResult]:
187
+ """Find stored chunks whose fingerprints appear anywhere in token_ids.
188
+
189
+ Uses a sliding-window rolling hash so matches need not be aligned to
190
+ chunk_size boundaries in the query. Entries previously evicted via
191
+ remove_chunks (token_hash set to None) are silently skipped.
192
+
193
+ Args:
194
+ token_ids: Query token sequence to probe (raw token IDs as uint64).
195
+
196
+ Returns:
197
+ One CBMatchResult per unique stored chunk that was hit.
198
+ old_st/old_ed : positions in the originally registered sequence
199
+ cur_st/cur_ed : positions in the query (token_ids) where
200
+ the match was found
201
+ hash : token_hash bytes (from registration) for cache key lookup
202
+ """
203
+ if not self._chunk_token_hash or len(token_ids) < self.chunk_size:
204
+ return []
205
+
206
+ arr = np.array(token_ids, dtype=np.uint64)
207
+
208
+ # Sliding-window polynomial hashes over the query
209
+ rolling = rolling_hash_windows_numba(arr, self.chunk_size, self._BASE)
210
+
211
+ # Probe table; seen array is _TABLE_SIZE bytes (~1 MB), fixed and safe
212
+ hit_ids = unique_hits_direct_id_numba(
213
+ rolling, self._table_id, self._mask, self._TABLE_SIZE
214
+ )
215
+
216
+ if hit_ids.shape[0] == 0:
217
+ return []
218
+
219
+ # For each hit compact_id, find the first query position where it matched
220
+ hit_id_set = set(int(cid) for cid in hit_ids)
221
+ cid_to_query_pos: dict[int, int] = {}
222
+ for q_pos in range(rolling.shape[0]):
223
+ idx = int(rolling[q_pos]) & int(self._mask)
224
+ cid = int(self._table_id[idx])
225
+ if cid in hit_id_set and cid not in cid_to_query_pos:
226
+ cid_to_query_pos[cid] = q_pos
227
+ if len(cid_to_query_pos) == len(hit_id_set):
228
+ break
229
+
230
+ results: list[CBMatchResult] = []
231
+ for cid in hit_ids:
232
+ cid_int = int(cid)
233
+ th = self._chunk_token_hash[cid_int]
234
+ if th is None:
235
+ continue
236
+ old_st = self._token_hash_to_start.get(th)
237
+ cur_st = cid_to_query_pos.get(cid_int)
238
+ if old_st is None or cur_st is None:
239
+ continue
240
+ results.append(
241
+ CBMatchResult(
242
+ old_st=old_st,
243
+ old_ed=old_st + self.chunk_size,
244
+ cur_st=cur_st,
245
+ cur_ed=cur_st + self.chunk_size,
246
+ hash=th,
247
+ )
248
+ )
249
+ return results
250
+
251
+ def remove_chunks(self, token_hashes: list[bytes]) -> None:
252
+ """Evict stale entries whose backing data is no longer in storage.
253
+
254
+ Args:
255
+ token_hashes: Token hashes of chunks to remove from the table.
256
+ """
257
+ for th in token_hashes:
258
+ cid = self._token_hash_to_compact_id.get(th)
259
+ if cid is None:
260
+ continue
261
+ # Clear the table slot
262
+ slot = int(self._compact_id_to_slot[cid])
263
+ if slot < 0:
264
+ logger.warning(
265
+ "compact_id %d has no valid table slot; "
266
+ "entry may have been evicted twice",
267
+ cid,
268
+ )
269
+ continue
270
+ self._table_id[slot] = -1
271
+ self._compact_id_to_slot[cid] = -1
272
+ # Clean up auxiliary maps
273
+ self._chunk_token_hash[cid] = None
274
+ self._token_hash_to_start.pop(th, None)
275
+ del self._token_hash_to_compact_id[th]
276
+
277
+
278
+ # Main class and main functions
279
+ class BlendEngineV2(MPCacheEngine):
280
+ def __init__(
281
+ self,
282
+ storage_manager_config: StorageManagerConfig,
283
+ chunk_size: int = 256,
284
+ hash_algorithm: str = "blake3",
285
+ ):
286
+ super().__init__(
287
+ storage_manager_config, chunk_size, hash_algorithm=hash_algorithm
288
+ )
289
+
290
+ self._cb_gpu_contexts: dict[int, PlainGPUCacheContext] = {}
291
+
292
+ # CB GPU ID -> (model name, world size) as metadata
293
+ # NOTE: This is mainly for determining the layout desc during prefetch
294
+ self._cb_gpu_context_meta: dict[int, tuple[str, int]] = {}
295
+
296
+ # Fast local matcher: indexes pre-computed chunk hashes for sub-sequence lookup
297
+ self._token_range_matcher = BlendTokenRangeMatcher(chunk_size)
298
+
299
+ def cb_register_kv_cache(
300
+ self,
301
+ instance_id: int,
302
+ kv_caches: KVCache,
303
+ model_name: str,
304
+ world_size: int,
305
+ ) -> None:
306
+ """
307
+ Register the KV cache buffer from the blend engine
308
+
309
+ Args:
310
+ instance_id: Unique identifier for the blend engine instance
311
+ kv_caches: KVCache object containing the GPU buffer pointers
312
+ model_name: The name of the model associated with this KV cache.
313
+ world_size: The world size associated with this KV cache.
314
+ """
315
+ gpu_context = PlainGPUCacheContext(kv_caches, self.chunk_size)
316
+ self._cb_gpu_contexts[instance_id] = gpu_context
317
+ self._cb_gpu_context_meta[instance_id] = (model_name, world_size)
318
+ logger.info(
319
+ "Registered CB KV cache for instance_id %d with %d layers",
320
+ instance_id,
321
+ gpu_context.num_layers,
322
+ )
323
+
324
+ def cb_unregister_kv_cache(self, instance_id: int) -> None:
325
+ """
326
+ Unregister the KV cache buffer for the given instance_id
327
+
328
+ Args:
329
+ instance_id: Unique identifier for the blend engine instance to unregister
330
+ """
331
+ if instance_id in self._cb_gpu_contexts:
332
+ del self._cb_gpu_contexts[instance_id]
333
+ del self._cb_gpu_context_meta[instance_id]
334
+ logger.info("Unregistered CB KV cache for instance_id %d", instance_id)
335
+ else:
336
+ logger.warning(
337
+ "Attempted to unregister non-existent CB KV cache for instance_id %d",
338
+ instance_id,
339
+ )
340
+
341
+ def cb_lookup_pre_computed(self, key: IPCCacheEngineKey) -> list[CBMatchResult]:
342
+ """
343
+ Lookup the pre-computed chunks in the underlying storage.
344
+
345
+ Uses BlendTokenRangeMatcher for a fast local pre-filter, then submits
346
+ prefetch tasks for matched chunks using their stored hashes directly.
347
+ Chunks that the fingerprint table matched but are no longer present in
348
+ storage are lazily evicted from the matcher via remove_chunks.
349
+
350
+ Args:
351
+ key: IPCCacheEngineKey containing the token ids to lookup
352
+
353
+ Returns:
354
+ List of CBMatchResult for chunks that were actually found in storage,
355
+ ready to be passed to cb_retrieve_pre_computed.
356
+ """
357
+ # Fast local pre-filter: find which stored chunks appear in this query
358
+ cb_match_result = self._token_range_matcher.match_sub_sequence(
359
+ list(key.token_ids)
360
+ )
361
+ if not cb_match_result:
362
+ return []
363
+
364
+ # Sort by query position and group consecutive matched chunks
365
+ cb_match_result.sort(key=lambda r: r.cur_st)
366
+ groups: list[list[CBMatchResult]] = []
367
+ for result in cb_match_result:
368
+ if groups and groups[-1][-1].cur_ed == result.cur_st:
369
+ groups[-1].append(result)
370
+ else:
371
+ groups.append([result])
372
+
373
+ prefetch_handles: list[PrefetchHandle] = []
374
+ found_cb_match_result: list[CBMatchResult] = []
375
+ model_name, world_size = key.model_name, key.world_size
376
+
377
+ # Find the cb gpu context and calculate the layout desc
378
+ layout_desc: MemoryLayoutDesc | None = None
379
+ for gpu_id, (m_name, w_size) in self._cb_gpu_context_meta.items():
380
+ if m_name == model_name and w_size == world_size:
381
+ cb_ctx = self._cb_gpu_contexts[gpu_id]
382
+ layout_desc = MemoryLayoutDesc(
383
+ shapes=[cb_ctx.get_kv_buffer_shape(self.chunk_size)],
384
+ dtypes=[cb_ctx.dtype],
385
+ )
386
+ break
387
+
388
+ if layout_desc is None:
389
+ logger.error(
390
+ "No CB GPU context found for model %s with world size %d "
391
+ "during cb_lookup_pre_computed!",
392
+ model_name,
393
+ world_size,
394
+ )
395
+ return []
396
+
397
+ # Submit prefetch for each group using CBMatchResult.hash directly
398
+ for group in groups:
399
+ chunk_hashes = [r.hash for r in group]
400
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
401
+ handle = self.storage_manager.submit_prefetch_task(
402
+ obj_keys,
403
+ layout_desc,
404
+ external_request_id=key.request_id,
405
+ )
406
+ prefetch_handles.append(handle)
407
+
408
+ logger.debug(
409
+ "DEBUG: Submitted prefetch for %d chunks starting at %d",
410
+ len(group),
411
+ group[0].cur_st,
412
+ )
413
+
414
+ # TODO(Jiayi): We need to follow how lookup is handled in server.py
415
+ # to optimize performance.
416
+ # Collect only the CBMatchResults for chunks actually found in storage
417
+ stale_hashes: list[bytes] = []
418
+ for handle, group in zip(prefetch_handles, groups, strict=False):
419
+ found_count = None
420
+ while True:
421
+ found_count = self.storage_manager.query_prefetch_status(handle)
422
+ if found_count is not None:
423
+ break
424
+
425
+ # Standard
426
+ import time
427
+
428
+ time.sleep(0.001)
429
+
430
+ # Real found count after dedup the TP
431
+ found_count = found_count // world_size
432
+
433
+ start = group[0].cur_st
434
+ end = group[-1].cur_ed
435
+ if found_count > 0:
436
+ found_cb_match_result.extend(group[:found_count])
437
+ # Chunks after found_count in the group are stale
438
+ stale_hashes.extend(r.hash for r in group[found_count:])
439
+ logger.debug(
440
+ "Found %d pre-computed chunks for range (%d, %d)",
441
+ found_count,
442
+ start,
443
+ end,
444
+ )
445
+ else:
446
+ stale_hashes.extend(r.hash for r in group)
447
+ logger.debug(
448
+ "No pre-computed chunks found for range (%d, %d)",
449
+ start,
450
+ end,
451
+ )
452
+
453
+ # Evict stale entries from the fingerprint table
454
+ if stale_hashes:
455
+ self._token_range_matcher.remove_chunks(stale_hashes)
456
+ logger.debug(
457
+ "Evicted %d stale chunks from fingerprint table",
458
+ len(stale_hashes),
459
+ )
460
+
461
+ return found_cb_match_result
462
+
463
+ def _cb_store_gpu_copy(
464
+ self,
465
+ obj_keys: list[ObjectKey],
466
+ gpu_context: PlainGPUCacheContext,
467
+ offset: int,
468
+ event_ipc_handle: bytes,
469
+ ) -> tuple[torch.cuda.Event, dict]:
470
+ """
471
+ Helper function to perform GPU-to-CPU copy operations for storing chunks.
472
+
473
+ Args:
474
+ obj_keys: List of object keys to store.
475
+ gpu_context: GPU context for the blend engine instance.
476
+ offset: The starting offset in the CB KV cache buffer.
477
+ event_ipc_handle: The IPC handle for the CUDA event that signals the
478
+ completion of LLM inference.
479
+
480
+ Returns:
481
+ A tuple of (event, reserved_dict) where event is the CUDA event and
482
+ reserved_dict is the dictionary of reserved memory objects.
483
+ """
484
+ with (
485
+ torch.cuda.device(gpu_context.device),
486
+ torch.cuda.stream(gpu_context.stream),
487
+ ):
488
+ event = torch.cuda.Event(interprocess=True)
489
+
490
+ # Wait for vLLM event to finish
491
+ vllm_event = torch.cuda.Event.from_ipc_handle(
492
+ gpu_context.device, event_ipc_handle
493
+ )
494
+ vllm_event.wait(stream=gpu_context.stream)
495
+
496
+ # Prepare for the copy
497
+ num_tokens = self.chunk_size
498
+ cpu_shape = gpu_context.get_kv_buffer_shape(num_tokens)
499
+ layout_desc = MemoryLayoutDesc(
500
+ shapes=[cpu_shape], dtypes=[gpu_context.dtype]
501
+ )
502
+
503
+ reserved_dict = self.storage_manager.reserve_write(
504
+ obj_keys, layout_desc, "new"
505
+ )
506
+
507
+ for idx, obj_key in enumerate(obj_keys):
508
+ if obj_key in reserved_dict:
509
+ memory_obj = reserved_dict[obj_key]
510
+ else:
511
+ continue
512
+
513
+ offset_start = idx * self.chunk_size + offset
514
+ offset_end = offset_start + self.chunk_size
515
+
516
+ # Copy from GPU to CPU
517
+ tmp_buffer = gpu_context.get_tmp_gpu_buffer(offset_end - offset_start)
518
+ gpu_kv_slice = gpu_context.slice_kv_cache_on_tokens(
519
+ offset_start, offset_end
520
+ )
521
+ with self.lock:
522
+ tmp_buffer.copy_(gpu_kv_slice, non_blocking=True)
523
+ lmcache_memcpy_async_d2h(tmp_buffer, memory_obj)
524
+
525
+ event.record()
526
+
527
+ # Call finish_write after the copy is done
528
+ gpu_context.cupy_stream.launch_host_func(
529
+ self.storage_manager.finish_write,
530
+ list(reserved_dict.keys()),
531
+ )
532
+
533
+ return event, reserved_dict
534
+
535
+ def cb_store_pre_computed(
536
+ self,
537
+ key: IPCCacheEngineKey,
538
+ offset: int,
539
+ instance_id: int,
540
+ event_ipc_handle: bytes,
541
+ ) -> tuple[bytes, bool]:
542
+ """
543
+ Store the pre-computed chunks in the underlying storage for later retrieval.
544
+
545
+ Args:
546
+ key: IPCCacheEngineKey containing the token ids for which the pre-computed
547
+ chunks are stored.
548
+ offset: The starting offset in the CB KV cache buffer where the
549
+ pre-computed
550
+ instance_id: The instance_id of the blend engine instance to store the
551
+ pre-computed chunks for.
552
+ event_ipc_handle: The IPC handle for the CUDA event that signals the
553
+ completion of LLM inference.
554
+
555
+ Returns:
556
+ IPC handle bytes for the event that signals the completion of storing the
557
+ pre-computed chunks, and a boolean flag indicating if the store is
558
+ successful.
559
+
560
+ Note:
561
+ The input tokens should not have any separator in it. It should just be
562
+ one "paragraph".
563
+ This function will discard the last partial chunk and only store the full
564
+ chunks
565
+ """
566
+ # Compute normal prefix hashes so these chunks are accessible both via
567
+ # the CB lookup path and via the standard lookup/retrieve path.
568
+ chunk_hashes = self.token_hasher.compute_chunk_hashes(list(key.token_ids))
569
+ # convert to object key
570
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
571
+
572
+ assert instance_id in self._cb_gpu_contexts, (
573
+ f"Instance ID {instance_id} not registered for CB KV cache"
574
+ )
575
+ gpu_context = self._cb_gpu_contexts[instance_id]
576
+
577
+ event, reserved_dict = self._cb_store_gpu_copy(
578
+ obj_keys, gpu_context, offset, event_ipc_handle
579
+ )
580
+
581
+ # Register chunk hashes with the local matcher for fast sub-sequence lookup
582
+ token_hashes = list(chunk_hashes)
583
+
584
+ # NOTE(Jiayi): We only register the token hashes for worker_id 0 or None to
585
+ # avoid duplicate registration across workers.
586
+ if key.worker_id in [0, None]:
587
+ self._token_range_matcher.on_new_token_hashes(
588
+ list(key.token_ids), token_hashes
589
+ )
590
+
591
+ logger.info(
592
+ "Stored pre-computed doc with %d tokens, num stored chunks: %d",
593
+ key.end - key.start,
594
+ len(reserved_dict),
595
+ )
596
+ return event.ipc_handle(), True
597
+
598
+ def cb_retrieve_pre_computed(
599
+ self,
600
+ key: IPCCacheEngineKey,
601
+ cb_match_result: list[CBMatchResult],
602
+ offset: int,
603
+ instance_id: int,
604
+ event_ipc_handle: bytes,
605
+ ) -> tuple[bytes, bool]:
606
+ """
607
+ Retrieve the pre-computed chunks from the underlying storage and copy them to
608
+ the CB KV cache buffer.
609
+
610
+ Args:
611
+ key: IPCCacheEngineKey containing the token ids for which the pre-computed
612
+ chunks are retrieved.
613
+ cb_match_result: List of CBMatchResult returned by cb_lookup_pre_computed,
614
+ containing the per-chunk hashes and query positions.
615
+ offset: The starting offset in the CB KV cache buffer to copy the retrieved
616
+ chunks to.
617
+ instance_id: The instance_id of the blend engine instance to retrieve the
618
+ pre-computed chunks for.
619
+ event_ipc_handle: The IPC handle for the CUDA event that signals the
620
+ completion of LLM inference.
621
+
622
+ Returns:
623
+ IPC handle bytes for the event that signals the completion of retrieving the
624
+ pre-computed chunks, and a boolean flag indicating if the retrieval is
625
+ successful.
626
+
627
+ Note:
628
+ We must call `cb_lookup_pre_computed` first before calling this function
629
+ """
630
+ assert instance_id in self._cb_gpu_contexts, (
631
+ f"Instance ID {instance_id} not registered for CB KV cache"
632
+ )
633
+ gpu_context = self._cb_gpu_contexts[instance_id]
634
+
635
+ # One obj_key per match_result, in cur_st order
636
+ cb_match_result = sorted(cb_match_result, key=lambda r: r.cur_st)
637
+ chunk_hashes = [r.hash for r in cb_match_result]
638
+ all_obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
639
+
640
+ logger.debug("DEBUG object keys to retrieve: %s", all_obj_keys)
641
+
642
+ with (
643
+ torch.cuda.device(gpu_context.device),
644
+ torch.cuda.stream(gpu_context.stream),
645
+ ):
646
+ event = torch.cuda.Event(interprocess=True)
647
+
648
+ try:
649
+ with self.storage_manager.read_prefetched_results(
650
+ all_obj_keys
651
+ ) as memory_objs:
652
+ if memory_objs is None:
653
+ logger.error("Some keys not found during CB retrieve!")
654
+ return event.ipc_handle(), False
655
+
656
+ for r, memory_obj in zip(
657
+ cb_match_result, memory_objs, strict=False
658
+ ):
659
+ gpu_st = r.cur_st + offset
660
+ gpu_ed = gpu_st + self.chunk_size
661
+ tmp_buffer = gpu_context.get_tmp_gpu_buffer(self.chunk_size)
662
+ target_buffer = gpu_context.slice_kv_cache_on_tokens(
663
+ gpu_st, gpu_ed
664
+ )
665
+ with self.lock:
666
+ lmcache_memcpy_async_h2d(memory_obj, tmp_buffer)
667
+ target_buffer.copy_(tmp_buffer, non_blocking=True)
668
+
669
+ except Exception:
670
+ logger.exception("Error during retrieving prefetched results")
671
+ return event.ipc_handle(), False
672
+
673
+ finally:
674
+ event.record()
675
+ # TODO: here we simply "unlock" all the keys, which may cause
676
+ # double-unlock if error happens during read_prefetched_results.
677
+ # We should consider not unlocking objects in read_prefetched_results
678
+ # if error happens.
679
+ gpu_context.cupy_stream.launch_host_func(
680
+ self.storage_manager.finish_read_prefetched, all_obj_keys
681
+ )
682
+
683
+ logger.info(
684
+ "Retrieved pre-computed for %d match results to GPU offset starting at %d",
685
+ len(cb_match_result),
686
+ offset,
687
+ )
688
+ return event.ipc_handle(), True
689
+
690
+ def cb_store_final(
691
+ self,
692
+ key: IPCCacheEngineKey,
693
+ offset: int,
694
+ instance_id: int,
695
+ event_ipc_handle: bytes,
696
+ ) -> tuple[bytes, bool]:
697
+ """
698
+ Store the final chunks in the underlying storage after processing. The stored
699
+ chunk should be accessible for normal mode LLMs.
700
+
701
+ Args:
702
+ key: IPCCacheEngineKey containing the token ids for which the final chunks
703
+ are stored.
704
+ offset: The starting offset in the CB KV cache buffer where the final
705
+ chunks are stored.
706
+ instance_id: The instance_id of the blend engine instance to store the final
707
+ chunks for.
708
+ event_ipc_handle: The IPC handle for the CUDA event that signals the
709
+ completion of LLM inference.
710
+
711
+ Returns:
712
+ IPC handle bytes for the event that signals the completion of storing the
713
+ final chunks, and a boolean flag indicating if the store is successful.
714
+ """
715
+ # Compute normal hash for the keys
716
+ chunk_hashes = self.token_hasher.compute_chunk_hashes(list(key.token_ids))
717
+
718
+ # convert to object key
719
+ obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
720
+
721
+ # Get GPU context
722
+ assert instance_id in self._cb_gpu_contexts, (
723
+ f"Instance ID {instance_id} not registered for CB KV cache"
724
+ )
725
+ gpu_context = self._cb_gpu_contexts[instance_id]
726
+
727
+ event, reserved_dict = self._cb_store_gpu_copy(
728
+ obj_keys, gpu_context, offset, event_ipc_handle
729
+ )
730
+
731
+ logger.info(
732
+ "Stored final doc with %d tokens, num stored chunks: %d",
733
+ key.end - key.start,
734
+ len(reserved_dict),
735
+ )
736
+ return event.ipc_handle(), True
737
+
738
+
739
+ def add_handler_helper(
740
+ server: MessageQueueServer, request_type: RequestType, handler_function
741
+ ):
742
+ payload_classes = get_payload_classes(request_type)
743
+ handler_type = get_handler_type(request_type)
744
+ server.add_handler(
745
+ request_type,
746
+ payload_classes,
747
+ handler_type,
748
+ handler_function,
749
+ )
750
+
751
+
752
+ def run_cache_server(
753
+ mp_config: MPServerConfig,
754
+ storage_manager_config: StorageManagerConfig,
755
+ obs_config: ObservabilityConfig,
756
+ return_engine: bool = False,
757
+ ):
758
+ """
759
+ Run the LMCache cache server with ZMQ message queue.
760
+
761
+ Args:
762
+ mp_config: Configuration for the ZMQ multiprocess server
763
+ storage_manager_config: Configuration for the storage manager
764
+ obs_config: Configuration for the observability stack
765
+ return_engine: If True, return (server, engine) after starting;
766
+ if False, run blocking loop to keep server alive
767
+
768
+ Returns:
769
+ If return_engine is True: tuple of (MessageQueueServer, BlendEngineV2)
770
+ If return_engine is False: None (blocks until interrupted)
771
+ """
772
+ event_bus = init_observability(obs_config)
773
+
774
+ # Wire up the trace recorder (no-op when --trace-level is unset).
775
+ maybe_initialize_trace_recorder(event_bus, obs_config, storage_manager_config)
776
+
777
+ # Initialize the engine (loggers self-register with the global controller)
778
+ engine = BlendEngineV2(
779
+ storage_manager_config=storage_manager_config,
780
+ chunk_size=mp_config.chunk_size,
781
+ hash_algorithm=mp_config.hash_algorithm,
782
+ )
783
+
784
+ # Initialize the message queue server
785
+ context = zmq.Context.instance()
786
+ server = MessageQueueServer(
787
+ bind_url=f"tcp://{mp_config.host}:{mp_config.port}",
788
+ context=context,
789
+ )
790
+
791
+ # Add handlers for original server
792
+ add_handler_helper(server, RequestType.REGISTER_KV_CACHE, engine.register_kv_cache)
793
+ add_handler_helper(
794
+ server, RequestType.UNREGISTER_KV_CACHE, engine.unregister_kv_cache
795
+ )
796
+ add_handler_helper(server, RequestType.STORE, engine.store)
797
+ add_handler_helper(server, RequestType.LOOKUP, engine.lookup)
798
+ add_handler_helper(
799
+ server, RequestType.QUERY_PREFETCH_STATUS, engine.query_prefetch_status
800
+ )
801
+ add_handler_helper(server, RequestType.FREE_LOOKUP_LOCKS, engine.free_lookup_locks)
802
+ add_handler_helper(server, RequestType.RETRIEVE, engine.retrieve)
803
+ add_handler_helper(server, RequestType.CLEAR, engine.clear)
804
+ add_handler_helper(server, RequestType.GET_CHUNK_SIZE, engine.get_chunk_size)
805
+ add_handler_helper(server, RequestType.END_SESSION, engine.end_session)
806
+ add_handler_helper(server, RequestType.NOOP, engine.debug)
807
+ add_handler_helper(
808
+ server,
809
+ RequestType.REPORT_BLOCK_ALLOCATION,
810
+ engine.report_block_allocations,
811
+ )
812
+ # Add handler for blend operations
813
+ add_handler_helper(
814
+ server, RequestType.CB_REGISTER_KV_CACHE, engine.cb_register_kv_cache
815
+ )
816
+ add_handler_helper(
817
+ server, RequestType.CB_UNREGISTER_KV_CACHE, engine.cb_unregister_kv_cache
818
+ )
819
+ add_handler_helper(
820
+ server, RequestType.CB_LOOKUP_PRE_COMPUTED_V2, engine.cb_lookup_pre_computed
821
+ )
822
+ add_handler_helper(
823
+ server, RequestType.CB_STORE_PRE_COMPUTED, engine.cb_store_pre_computed
824
+ )
825
+ add_handler_helper(
826
+ server, RequestType.CB_RETRIEVE_PRE_COMPUTED_V2, engine.cb_retrieve_pre_computed
827
+ )
828
+ add_handler_helper(server, RequestType.CB_STORE_FINAL, engine.cb_store_final)
829
+ add_handler_helper(server, RequestType.PING, engine.ping)
830
+
831
+ # Assign thread pools
832
+ server.add_affinity_thread_pool(
833
+ [
834
+ RequestType.STORE,
835
+ RequestType.RETRIEVE,
836
+ RequestType.CB_STORE_PRE_COMPUTED,
837
+ RequestType.CB_RETRIEVE_PRE_COMPUTED_V2,
838
+ RequestType.CB_STORE_FINAL,
839
+ ],
840
+ max_workers=mp_config.max_gpu_workers,
841
+ )
842
+ server.add_normal_thread_pool(
843
+ [
844
+ RequestType.LOOKUP,
845
+ RequestType.QUERY_PREFETCH_STATUS,
846
+ RequestType.FREE_LOOKUP_LOCKS,
847
+ RequestType.END_SESSION,
848
+ RequestType.CLEAR,
849
+ RequestType.CB_LOOKUP_PRE_COMPUTED_V2,
850
+ RequestType.PING,
851
+ RequestType.REPORT_BLOCK_ALLOCATION,
852
+ ],
853
+ max_workers=mp_config.max_cpu_workers,
854
+ )
855
+
856
+ logger.info(
857
+ "LMCache ZMQ cache server is running on tcp://%s:%d",
858
+ mp_config.host,
859
+ mp_config.port,
860
+ )
861
+ # Start the ZMQ server
862
+ torch.cuda.init()
863
+ server.start()
864
+
865
+ logger.info("LMCache cache blend v2 server is running...")
866
+
867
+ # Return server and engine if requested (for HTTP server integration)
868
+ if return_engine:
869
+ return server, engine
870
+
871
+ # Dummy loop to keep the server running
872
+ try:
873
+ while True:
874
+ time.sleep(1)
875
+ except KeyboardInterrupt:
876
+ logger.info("Shutting down server...")
877
+ event_bus.stop()
878
+ server.close()
879
+ engine.close()
880
+
881
+
882
+ if __name__ == "__main__":
883
+ args = parse_args()
884
+ mp_config = parse_args_to_mp_server_config(args)
885
+ storage_manager_config = parse_args_to_config(args)
886
+ obs_config = parse_args_to_observability_config(args)
887
+ run_cache_server(
888
+ mp_config=mp_config,
889
+ storage_manager_config=storage_manager_config,
890
+ obs_config=obs_config,
891
+ )