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,1072 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ import enum
4
+ import inspect
5
+ from collections.abc import Iterable
6
+ from dataclasses import dataclass, field
7
+ from typing import TYPE_CHECKING, Any, Literal
8
+
9
+ import torch
10
+ import zmq
11
+ from lmcache.integration.vllm.utils import mla_enabled
12
+ from lmcache.utils import init_logger as lmcache_init_logger
13
+
14
+ from vllm.config import VllmConfig
15
+ from vllm.distributed.kv_transfer.kv_connector.v1.base import (
16
+ KVConnectorBase_V1,
17
+ KVConnectorMetadata,
18
+ KVConnectorRole,
19
+ )
20
+ from vllm.v1.attention.backend import AttentionMetadata
21
+ from vllm.v1.core.sched.output import SchedulerOutput
22
+ from vllm.v1.outputs import KVConnectorOutput
23
+ from vllm.v1.request import RequestStatus
24
+ from vllm.v1.utils import ConstantList
25
+
26
+ try:
27
+ from lmcache.integration.vllm.vllm_multi_process_adapter import (
28
+ LMCacheMPSchedulerAdapter,
29
+ LMCacheMPWorkerAdapter,
30
+ LoadStoreOp,
31
+ )
32
+ except ImportError:
33
+ from vllm.distributed.kv_transfer.kv_connector.v1.lmcache_integration import (
34
+ LMCacheMPSchedulerAdapter,
35
+ LMCacheMPWorkerAdapter,
36
+ LoadStoreOp,
37
+ )
38
+
39
+ if TYPE_CHECKING:
40
+ from vllm.distributed.kv_events import KVCacheEvent
41
+ from vllm.distributed.kv_transfer.kv_connector.v1.metrics import (
42
+ KVConnectorPromMetrics,
43
+ KVConnectorStats,
44
+ PromMetric,
45
+ PromMetricT,
46
+ )
47
+ from vllm.forward_context import ForwardContext
48
+ from vllm.v1.core.kv_cache_manager import KVCacheBlocks
49
+ from vllm.v1.core.kv_cache_utils import BlockHash
50
+ from vllm.v1.kv_cache_interface import KVCacheConfig
51
+ from vllm.v1.request import Request
52
+
53
+ logger = lmcache_init_logger(__name__)
54
+
55
+
56
+ def _adapter_accepts_tp_size() -> bool:
57
+ """Check if the imported adapter accepts tp_size."""
58
+ sig = inspect.signature(LMCacheMPSchedulerAdapter.__init__)
59
+ return "tp_size" in sig.parameters
60
+
61
+
62
+ # Helper functions
63
+ def reformat_block_ids(block_ids: tuple[list[int], ...] | None) -> list[int]:
64
+ if block_ids is None:
65
+ return []
66
+ assert isinstance(block_ids, tuple), (
67
+ f"Expected block_ids to be a tuple of lists, but got {type(block_ids)}"
68
+ )
69
+
70
+ if len(block_ids) > 1:
71
+ raise RuntimeError(
72
+ "LMCacheMPConnector only works without hybrid kv cache manager. "
73
+ "Please pass --disable-hybrid-kv-cache-manager when starting vllm"
74
+ )
75
+
76
+ return block_ids[0]
77
+
78
+
79
+ def extract_world_size_and_kv_rank(
80
+ world_size: int,
81
+ rank: int,
82
+ vllm_config: VllmConfig,
83
+ ) -> tuple[int, int]:
84
+ """
85
+ Convert the rank for the MLA.
86
+ """
87
+ use_mla = mla_enabled(vllm_config.model_config)
88
+ if not use_mla:
89
+ return world_size, rank
90
+ else:
91
+ # Tensor parallel does not change the KV caches for MLA models.
92
+ # So we need to "exclude" the effect of TP on rank and world size
93
+ tp_size = vllm_config.parallel_config.tensor_parallel_size
94
+ # vLLM constructs TP groups first, and then construct other
95
+ # parallel groups on top of TP groups.
96
+ # for example, TP=4, PP=2,
97
+ # TP group: [0, 1, 2, 3], [4, 5, 6, 7]
98
+ # PP group: [0, 4], [1, 5], [2, 6], [3, 7]
99
+ # So we can "exclude" the effect of TP by rank // tp_size.
100
+ return world_size // tp_size, rank // tp_size
101
+
102
+
103
+ def create_scheduler_adapter(
104
+ server_url: str,
105
+ zmq_context: zmq.Context,
106
+ vllm_config: VllmConfig,
107
+ mq_timeout: float,
108
+ heartbeat_interval: float,
109
+ ) -> LMCacheMPSchedulerAdapter:
110
+ world_size, kv_rank = extract_world_size_and_kv_rank(
111
+ vllm_config.parallel_config.world_size,
112
+ vllm_config.parallel_config.rank,
113
+ vllm_config,
114
+ )
115
+ tp_size = vllm_config.parallel_config.tensor_parallel_size
116
+
117
+ # Pass tp_size only when the adapter accepts it so that
118
+ # a newer vllm can still work with an older LMCache.
119
+ kwargs: dict[str, Any] = {}
120
+ if _adapter_accepts_tp_size():
121
+ kwargs["tp_size"] = tp_size
122
+
123
+ return LMCacheMPSchedulerAdapter(
124
+ server_url,
125
+ zmq_context,
126
+ vllm_config.model_config.model,
127
+ world_size,
128
+ kv_rank,
129
+ vllm_config.cache_config.block_size,
130
+ mq_timeout=mq_timeout,
131
+ heartbeat_interval=heartbeat_interval,
132
+ **kwargs,
133
+ )
134
+
135
+
136
+ def create_worker_adapter(
137
+ server_url: str,
138
+ zmq_context: zmq.Context,
139
+ vllm_config: VllmConfig,
140
+ mq_timeout: float,
141
+ heartbeat_interval: float,
142
+ ) -> LMCacheMPWorkerAdapter:
143
+ world_size, kv_rank = extract_world_size_and_kv_rank(
144
+ vllm_config.parallel_config.world_size,
145
+ vllm_config.parallel_config.rank,
146
+ vllm_config,
147
+ )
148
+ return LMCacheMPWorkerAdapter(
149
+ server_url,
150
+ zmq_context,
151
+ vllm_config.model_config.model,
152
+ world_size,
153
+ kv_rank,
154
+ vllm_config.cache_config.block_size,
155
+ mq_timeout=mq_timeout,
156
+ heartbeat_interval=heartbeat_interval,
157
+ )
158
+
159
+
160
+ class LMCacheMPRequestState(enum.Enum):
161
+ """
162
+ State machine:
163
+ PREFETCHING -- update_state_after_alloc --> WAITING_FOR_LOAD
164
+ WAITING_FOR_LOAD -- process_loading_requests --> READY
165
+ """
166
+
167
+ PREFETCHING = enum.auto()
168
+ WAITING_FOR_LOAD = enum.auto()
169
+ READY = enum.auto()
170
+
171
+
172
+ @dataclass
173
+ class LMCacheMPRequestTracker:
174
+ # NOTE: this class used vLLM data structures, should be part of
175
+ # vLLM integration code
176
+
177
+ request_id: str
178
+
179
+ # Read-only lists to track the token ids and block hashes
180
+ all_token_ids: ConstantList[int]
181
+ block_hashes: ConstantList["BlockHash"]
182
+
183
+ # Block ids and hashes will be updated at update_states_after_alloc and
184
+ # during the generation
185
+ allocated_block_ids: list[int] = field(default_factory=list)
186
+
187
+ # Number of scheduled tokens in this request. We keep tracking this to
188
+ # avoid saving half-full blocks.
189
+ num_scheduled_tokens: int = 0
190
+
191
+ # Number of blocks stored will be initialized when lookup the external
192
+ # hit tokens and will be updated when processing new requests and cached
193
+ # requests.
194
+ num_stored_blocks: int = 0
195
+
196
+ # Staging load operation -- save vllm and lmcache hit tokens during lookup
197
+ num_vllm_hit_blocks: int = 0
198
+ num_lmcache_hit_blocks: int = 0
199
+
200
+ # Main state
201
+ state: LMCacheMPRequestState = LMCacheMPRequestState.PREFETCHING
202
+
203
+ def __init__(self, request: "Request"):
204
+ self.request_id = request.request_id
205
+ self.all_token_ids = request.all_token_ids
206
+ self.block_hashes = ConstantList(request.block_hashes)
207
+ self.allocated_block_ids = []
208
+ self.num_stored_blocks = 0
209
+ self.num_vllm_hit_blocks = 0
210
+ self.num_lmcache_hit_blocks = 0
211
+ self.state = LMCacheMPRequestState.PREFETCHING
212
+
213
+ ####
214
+ # Check the state of the request
215
+ ####
216
+ def needs_retrieve(self) -> bool:
217
+ """Check whether the current request needs retrieve, will be used
218
+ update_stage_after_alloc"""
219
+ return (
220
+ self.num_lmcache_hit_blocks > self.num_vllm_hit_blocks
221
+ and self.state != LMCacheMPRequestState.READY
222
+ )
223
+
224
+ def is_ready_for_retrieving(self) -> bool:
225
+ """Check whether the current request is ready for retrieving,
226
+ will be used in process_loading_requests"""
227
+ return (
228
+ self.state == LMCacheMPRequestState.WAITING_FOR_LOAD
229
+ and self.needs_retrieve()
230
+ )
231
+
232
+ ####
233
+ # Update internal states
234
+ ####
235
+ def increase_num_scheduled_tokens(self, num_new_tokens: int):
236
+ self.num_scheduled_tokens += num_new_tokens
237
+
238
+ def increase_num_stored_blocks(self, num_new_blocks: int):
239
+ """Increase the number of stored blocks for the current request
240
+ This function will be called when processing the cached requests.
241
+ """
242
+ self.num_stored_blocks += num_new_blocks
243
+
244
+ def append_block_ids(
245
+ self,
246
+ new_block_ids: list[int],
247
+ ):
248
+ """Update the block ids for the current request
249
+ This function will be called when processing the cached requests.
250
+ """
251
+ self.allocated_block_ids.extend(new_block_ids)
252
+
253
+ ####
254
+ # For debugging
255
+ ####
256
+ def __repr__(self) -> str:
257
+ return (
258
+ f"LMCacheMPRequestTracker(request_id={self.request_id}, "
259
+ f"num_tokens={len(self.all_token_ids)}, "
260
+ f"num_block_hashes={len(self.block_hashes)}, "
261
+ f"num_allocated_blocks={len(self.allocated_block_ids)}, "
262
+ f"num_stored_blocks={self.num_stored_blocks}, "
263
+ f"vllm_hit_blocks={self.num_vllm_hit_blocks}, "
264
+ f"lmcache_hit_blocks={self.num_lmcache_hit_blocks}, "
265
+ f"state={self.state})"
266
+ )
267
+
268
+ def __str__(self) -> str:
269
+ return self.__repr__()
270
+
271
+
272
+ @dataclass
273
+ class LMCacheMPRequestMetadata:
274
+ request_id: str
275
+ direction: Literal["STORE", "RETRIEVE"]
276
+ op: LoadStoreOp
277
+
278
+ @staticmethod
279
+ def GetStoreMetadata(
280
+ tracker: LMCacheMPRequestTracker,
281
+ blocks_in_chunk: int,
282
+ vllm_block_size: int,
283
+ ) -> "LMCacheMPRequestMetadata | None":
284
+ """
285
+ Generate the store metadata for the current request tracker.
286
+
287
+ Args:
288
+ tracker: The request tracker to generate the metadata from.
289
+ blocks_in_chunk: the number of blocks in a LMCache data chunk
290
+ vllm_block_size: the block size used in vLLM
291
+ """
292
+ # Store the blocks that has block hashes
293
+ # NOTE: the invariant here is that `num_stored_blocks` should
294
+ # always be a multiple of `blocks_in_chunk`
295
+ # TODO: This should be checked everytime we update the num_stored_blocks
296
+ #
297
+ # Why computed_blocks uses max(num_vllm_hit_blocks, num_lmcache_hit_blocks):
298
+ #
299
+ # Both values represent a prefix of blocks whose KV data is already
300
+ # available (either from vLLM APC or from LMCache), so they must NOT
301
+ # be summed (that would double-count the overlapping prefix).
302
+ #
303
+ # * num_lmcache_hit_blocks: LMCache-hit blocks are already counted in
304
+ # num_stored_blocks (set during lookup), so they must be included
305
+ # here to keep the upper bound consistent. They are NOT re-stored.
306
+ # * num_vllm_hit_blocks: LMCache stores in units of chunks (N blocks),
307
+ # so num_lmcache_hit_blocks is rounded DOWN to the nearest chunk
308
+ # boundary. When vLLM APC hits more blocks than that rounded value
309
+ # (e.g. APC=44 blocks, LMCache=32 blocks after chunk alignment),
310
+ # using only num_lmcache_hit_blocks would set the upper bound too
311
+ # low and silently skip the APC-hit blocks that fall between the
312
+ # two values, causing under-storing. Taking the max ensures we
313
+ # always use the tighter (larger) of the two hit counts.
314
+ computed_blocks = tracker.num_scheduled_tokens // vllm_block_size + max(
315
+ tracker.num_vllm_hit_blocks, tracker.num_lmcache_hit_blocks
316
+ )
317
+ min_available_blocks = min(
318
+ len(tracker.block_hashes),
319
+ len(tracker.allocated_block_ids),
320
+ computed_blocks,
321
+ )
322
+ num_staging_blocks = min_available_blocks - tracker.num_stored_blocks
323
+ num_chunks = num_staging_blocks // blocks_in_chunk
324
+
325
+ if num_chunks >= 1:
326
+ start = tracker.num_stored_blocks
327
+ end = start + num_chunks * blocks_in_chunk
328
+ block_ids = tracker.allocated_block_ids[start:end]
329
+ start_token_idx = start * vllm_block_size
330
+ end_token_idx = end * vllm_block_size
331
+ token_ids = list(tracker.all_token_ids)
332
+ op = LoadStoreOp(
333
+ token_ids=token_ids,
334
+ block_ids=block_ids,
335
+ start=start_token_idx,
336
+ end=end_token_idx,
337
+ )
338
+
339
+ ret = LMCacheMPRequestMetadata(
340
+ request_id=tracker.request_id,
341
+ direction="STORE",
342
+ op=op,
343
+ )
344
+
345
+ # Update the request tracker
346
+ tracker.increase_num_stored_blocks(end - start)
347
+ return ret
348
+
349
+ return None
350
+
351
+ @staticmethod
352
+ def GetRetrieveMetadata(
353
+ tracker: LMCacheMPRequestTracker,
354
+ blocks_in_chunk: int,
355
+ vllm_block_size: int,
356
+ ) -> "LMCacheMPRequestMetadata | None":
357
+ """
358
+ Generate the retrieve metadata for the current request tracker.
359
+
360
+ Args:
361
+ tracker: The request tracker to generate the metadata from.
362
+ blocks_in_chunk: the number of blocks in a LMCache data chunk
363
+ vllm_block_size: the block size used in vLLM
364
+ """
365
+ if not tracker.is_ready_for_retrieving():
366
+ return None
367
+
368
+ # |---------------------|-----------------|----------------|
369
+ # | num_vllm_hit_blocks |
370
+ # | lmcache chunk 1 | lmcache chunk 2 |
371
+ # | need to retrieve |
372
+
373
+ start = tracker.num_vllm_hit_blocks // blocks_in_chunk * blocks_in_chunk
374
+ end = tracker.num_lmcache_hit_blocks
375
+ assert end % blocks_in_chunk == 0, (
376
+ "The number of LMCache hit blocks should be a multiple of the "
377
+ "number of blocks in a lmcache chunk. "
378
+ )
379
+ assert len(tracker.block_hashes) >= end, (
380
+ "The number of block hashes should be greater than or equal to the "
381
+ "number of LMCache hit blocks. "
382
+ )
383
+ if end > start:
384
+ block_ids = tracker.allocated_block_ids[start:end]
385
+ start_token_idx = start * vllm_block_size
386
+ end_token_idx = end * vllm_block_size
387
+ token_ids = list(tracker.all_token_ids)
388
+
389
+ # Compute how many tokens at the start of the retrieve range
390
+ # overlap with APC-shared blocks. The server must skip writing
391
+ # to these positions to avoid a cross-stream data race: the
392
+ # retrieve writes on the LMCache CUDA stream while concurrent
393
+ # requests may read these APC-shared blocks on the vLLM stream.
394
+ apc_overlap_blocks = tracker.num_vllm_hit_blocks - start
395
+ skip_first_n_tokens = apc_overlap_blocks * vllm_block_size
396
+
397
+ op = LoadStoreOp(
398
+ token_ids=token_ids,
399
+ block_ids=block_ids,
400
+ start=start_token_idx,
401
+ end=end_token_idx,
402
+ skip_first_n_tokens=skip_first_n_tokens,
403
+ )
404
+
405
+ ret = LMCacheMPRequestMetadata(
406
+ request_id=tracker.request_id,
407
+ direction="RETRIEVE",
408
+ op=op,
409
+ )
410
+ return ret
411
+
412
+ return None
413
+
414
+
415
+ class LMCacheMPConnectorMetadata(KVConnectorMetadata):
416
+ def __init__(self):
417
+ super().__init__()
418
+ self.requests: list[LMCacheMPRequestMetadata] = []
419
+
420
+ def add_request_metadata(self, request_metadata: LMCacheMPRequestMetadata):
421
+ self.requests.append(request_metadata)
422
+
423
+ def __len__(self):
424
+ return len(self.requests)
425
+
426
+ # For debugging
427
+ def __str__(self):
428
+ request_strs = []
429
+ for req_meta in self.requests:
430
+ request_strs.append(
431
+ f"RequestMetadata(request_id={req_meta.request_id}, "
432
+ f"direction={req_meta.direction}, "
433
+ f"num_blocks={len(req_meta.op)}, "
434
+ f"block_ids={req_meta.op.block_ids})"
435
+ )
436
+ return "[" + "\n".join(request_strs) + "]"
437
+
438
+ def __repr__(self):
439
+ return self.__str__()
440
+
441
+
442
+ class LMCacheMPConnector(KVConnectorBase_V1):
443
+ """
444
+ The connector for LMCache multi-process mode.
445
+
446
+ Extra configs (kv_transfer_config.extra_config):
447
+ - lmcache.mp.host: the host of the LMCache server.
448
+ - lmcache.mp.port: the port of the LMCache server.
449
+ - lmcache.mp.mq_timeout: timeout (seconds) for message queue requests.
450
+ - lmcache.mp.heartbeat_interval: interval (seconds) between server
451
+ heartbeat pings.
452
+ """
453
+
454
+ def __init__(
455
+ self,
456
+ vllm_config: "VllmConfig",
457
+ role: KVConnectorRole,
458
+ kv_cache_config: "KVCacheConfig | None" = None,
459
+ ):
460
+ super().__init__(vllm_config, role, kv_cache_config)
461
+
462
+ assert vllm_config.kv_transfer_config is not None
463
+ server_host = vllm_config.kv_transfer_config.get_from_extra_config(
464
+ "lmcache.mp.host", "tcp://localhost"
465
+ )
466
+ server_port = vllm_config.kv_transfer_config.get_from_extra_config(
467
+ "lmcache.mp.port", 5555
468
+ )
469
+ mq_timeout = float(
470
+ vllm_config.kv_transfer_config.get_from_extra_config(
471
+ "lmcache.mp.mq_timeout", 300.0
472
+ )
473
+ )
474
+ heartbeat_interval = float(
475
+ vllm_config.kv_transfer_config.get_from_extra_config(
476
+ "lmcache.mp.heartbeat_interval", 10.0
477
+ )
478
+ )
479
+
480
+ server_url = f"{server_host}:{server_port}"
481
+ zmq_context = zmq.Context.instance()
482
+ if self.role == KVConnectorRole.SCHEDULER:
483
+ self.scheduler_adapter = create_scheduler_adapter(
484
+ server_url,
485
+ zmq_context,
486
+ vllm_config,
487
+ mq_timeout,
488
+ heartbeat_interval,
489
+ )
490
+ self.request_trackers: dict[str, LMCacheMPRequestTracker] = {}
491
+ elif self.role == KVConnectorRole.WORKER:
492
+ self.worker_adapter = create_worker_adapter(
493
+ server_url,
494
+ zmq_context,
495
+ vllm_config,
496
+ mq_timeout,
497
+ heartbeat_interval,
498
+ )
499
+ else:
500
+ raise ValueError(f"Unknown KVConnectorRole: {self.role}")
501
+
502
+ self.vllm_block_size = vllm_config.cache_config.block_size
503
+
504
+ @property
505
+ def role(self) -> KVConnectorRole:
506
+ return self._role
507
+
508
+ # ==============================
509
+ # Worker-side methods
510
+ # ==============================
511
+
512
+ def _get_connector_metadata(self) -> KVConnectorMetadata:
513
+ """Get the connector metadata.
514
+
515
+ This function should only be called inside the connector.
516
+
517
+ Returns:
518
+ ConnectorMetadata: the connector metadata.
519
+ """
520
+
521
+ # Should only be called while set to valid metadata.
522
+ assert self._connector_metadata is not None
523
+ return self._connector_metadata
524
+
525
+ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
526
+ """
527
+ Initialize with the KV caches. Useful for pre-registering the
528
+ KV Caches in the KVConnector (e.g. for NIXL).
529
+
530
+ Args:
531
+ kv_caches: dictionary of layer names, kv cache
532
+ """
533
+ logger.info("Registering kv caches!")
534
+ self.worker_adapter.register_kv_caches(kv_caches)
535
+ return
536
+
537
+ def start_load_kv(self, forward_context: "ForwardContext", **kwargs: Any) -> None:
538
+ """
539
+ Start loading the KV cache from the connector to vLLM's paged
540
+ KV buffer. This is called from the forward context before the
541
+ forward pass to enable async loading during model execution.
542
+
543
+ Args:
544
+ forward_context (ForwardContext): the forward context.
545
+ **kwargs: additional arguments for the load operation
546
+
547
+ Note:
548
+ The number of elements in kv_caches and layer_names should be
549
+ the same.
550
+
551
+ """
552
+ metadata = self._get_connector_metadata()
553
+ assert isinstance(metadata, LMCacheMPConnectorMetadata)
554
+
555
+ request_ids = []
556
+ ops = []
557
+
558
+ for meta in metadata.requests:
559
+ if meta.direction != "RETRIEVE":
560
+ continue
561
+ request_ids.append(meta.request_id)
562
+ ops.append(meta.op)
563
+
564
+ if len(request_ids) == 0:
565
+ return
566
+
567
+ with torch.cuda.stream(torch.cuda.current_stream()):
568
+ event = torch.cuda.Event(interprocess=True)
569
+ event.record()
570
+
571
+ self.worker_adapter.batched_submit_retrieve_requests(request_ids, ops, event)
572
+
573
+ def wait_for_layer_load(self, layer_name: str) -> None:
574
+ """
575
+ Block until the KV for a specific layer is loaded into vLLM's
576
+ paged buffer. This is called from within attention layer to ensure
577
+ async copying from start_load_kv is complete.
578
+
579
+ This interface will be useful for layer-by-layer pipelining.
580
+
581
+ Args:
582
+ layer_name: the name of that layer
583
+ """
584
+ return
585
+
586
+ def save_kv_layer(
587
+ self,
588
+ layer_name: str,
589
+ kv_layer: torch.Tensor,
590
+ attn_metadata: AttentionMetadata,
591
+ **kwargs: Any,
592
+ ) -> None:
593
+ """
594
+ Start saving a layer of KV cache from vLLM's paged buffer
595
+ to the connector. This is called from within attention layer to
596
+ enable async copying during execution.
597
+
598
+ Args:
599
+ layer_name (str): the name of the layer.
600
+ kv_layer (torch.Tensor): the paged KV buffer of the current
601
+ layer in vLLM.
602
+ attn_metadata (AttentionMetadata): the attention metadata.
603
+ **kwargs: additional arguments for the save operation.
604
+ """
605
+ return
606
+
607
+ def wait_for_save(self):
608
+ """
609
+ Block until all the save operations is done. This is called
610
+ as the forward context exits to ensure that the async saving
611
+ from save_kv_layer is complete before finishing the forward.
612
+
613
+ This prevents overwrites of paged KV buffer before saving done.
614
+ """
615
+ metadata = self._get_connector_metadata()
616
+ assert isinstance(metadata, LMCacheMPConnectorMetadata)
617
+
618
+ request_ids = []
619
+ ops = []
620
+ for meta in metadata.requests:
621
+ if meta.direction != "STORE":
622
+ continue
623
+ request_ids.append(meta.request_id)
624
+ ops.append(meta.op)
625
+
626
+ if len(request_ids) == 0:
627
+ return
628
+
629
+ with torch.cuda.stream(torch.cuda.current_stream()):
630
+ event = torch.cuda.Event(interprocess=True)
631
+ event.record()
632
+
633
+ self.worker_adapter.batched_submit_store_requests(request_ids, ops, event)
634
+
635
+ def get_finished(
636
+ self, finished_req_ids: set[str]
637
+ ) -> tuple[set[str] | None, set[str] | None]:
638
+ """
639
+ Notifies worker-side connector ids of requests that have
640
+ finished generating tokens on the worker.
641
+ The scheduler process (via the Executors) will use this output
642
+ to track which workers are done.
643
+
644
+ Returns:
645
+ ids of requests that have finished asynchronous transfer
646
+ (requests that previously returned True from request_finished()),
647
+ tuple of (sending/saving ids, recving/loading ids).
648
+ The finished saves/sends req ids must belong to a set provided in a
649
+ call to this method (this call or a prior one).
650
+ """
651
+ val = self.worker_adapter.get_finished(finished_req_ids)
652
+ # logger.error("Finished req ids: %s, %s", val[0], val[1])
653
+ return val
654
+
655
+ def get_block_ids_with_load_errors(self) -> set[int]:
656
+ """
657
+ Get the set of block IDs that failed to load.
658
+
659
+ Returns:
660
+ Set of block IDs that encountered load errors.
661
+ Empty set if no load errors occurred.
662
+
663
+ Notes:
664
+ - Applies to both sync- and async-loading requests.
665
+ - Async loading: failed blocks may be reported in any forward pass
666
+ up to and including the pass where the request ID is returned by
667
+ `get_finished()`. Even if failures occur, the request must still
668
+ be reported via `get_finished()`, and the failed block IDs must
669
+ appear here no later than that same pass.
670
+ - Sync loading: failed blocks should be reported in the forward
671
+ pass in which they are detected.
672
+ """
673
+ return self.worker_adapter.get_block_ids_with_load_errors()
674
+
675
+ def shutdown(self):
676
+ """
677
+ Shutdown the connector. This is called when the worker process
678
+ is shutting down to ensure that all the async operations are
679
+ completed and the connector is cleaned up properly.
680
+ """
681
+ if hasattr(self, "worker_adapter"):
682
+ self.worker_adapter.shutdown()
683
+ return None
684
+
685
+ def get_kv_connector_stats(self) -> "KVConnectorStats | None":
686
+ """
687
+ Get the KV connector stats collected during the last interval.
688
+ """
689
+ return None
690
+
691
+ # ==============================
692
+ # Scheduler-side methods
693
+ # ==============================
694
+
695
+ def get_num_new_matched_tokens(
696
+ self,
697
+ request: "Request",
698
+ num_computed_tokens: int,
699
+ ) -> tuple[int | None, bool]:
700
+ """
701
+ Get number of new tokens that can be loaded from the
702
+ external KV cache beyond the num_computed_tokens.
703
+
704
+ Args:
705
+ request (Request): the request object.
706
+ num_computed_tokens (int): the number of locally
707
+ computed tokens for this request
708
+
709
+ Returns:
710
+ A tuple with the following elements:
711
+ - An optional number of tokens that can be loaded from the
712
+ external KV cache beyond what is already computed.
713
+ If None, it means that the connector needs more time to
714
+ determine the number of matched tokens, and the scheduler
715
+ should query for this request again later.
716
+ - `True` if external KV cache tokens will be loaded
717
+ asynchronously (between scheduler steps). Must be
718
+ 'False' if the first element is 0.
719
+
720
+ Notes:
721
+ The connector should only consider the largest prefix of prompt-
722
+ tokens for which KV cache is actually available at the time of the
723
+ call. If the cache cannot be loaded for some tokens (e.g., due to
724
+ connectivity issues or eviction), those tokens must not be taken
725
+ into account.
726
+ """
727
+ tracker = self._get_or_create_request_tracker(request)
728
+ # TODO: support loading KV for preempted requests in the future
729
+ if request.status == RequestStatus.PREEMPTED:
730
+ return 0, False
731
+
732
+ self.scheduler_adapter.maybe_submit_lookup_request(
733
+ request.request_id,
734
+ token_ids=list(request.all_token_ids),
735
+ )
736
+
737
+ ret = self.scheduler_adapter.check_lookup_result(request.request_id)
738
+ if ret is None:
739
+ return None, True
740
+
741
+ if ret == 0:
742
+ return 0, False
743
+
744
+ assert (
745
+ ret % (self.scheduler_adapter.num_blocks_per_chunk() * self.vllm_block_size)
746
+ == 0
747
+ )
748
+
749
+ # Update num stored blocks for the tracker
750
+ num_vllm_blocks = num_computed_tokens // self.vllm_block_size
751
+ num_lmcache_blocks = ret // self.vllm_block_size
752
+ tracker.increase_num_stored_blocks(num_lmcache_blocks)
753
+
754
+ # Save the vllm and lmcache hit tokens
755
+ tracker.num_vllm_hit_blocks = num_vllm_blocks
756
+ tracker.num_lmcache_hit_blocks = num_lmcache_blocks
757
+
758
+ need_to_load = max(0, ret - num_computed_tokens)
759
+ logger.debug(
760
+ "vLLM hit is: %d, Need to load is %d", num_computed_tokens, need_to_load
761
+ )
762
+ return need_to_load, need_to_load > 0
763
+
764
+ def update_state_after_alloc(
765
+ self, request: "Request", blocks: "KVCacheBlocks", num_external_tokens: int
766
+ ):
767
+ """
768
+ Update KVConnector state after block allocation.
769
+
770
+ If get_num_new_matched_tokens previously returned True for a
771
+ request, this function may be called twice for that same request -
772
+ first when blocks are allocated for the connector tokens to be
773
+ asynchronously loaded into, and second when any additional blocks
774
+ are allocated, after the load/transfer is complete.
775
+
776
+ Args:
777
+ request (Request): the request object.
778
+ blocks (KVCacheBlocks): the blocks allocated for the request.
779
+ num_external_tokens (int): the number of tokens that will be
780
+ loaded from the external KV cache.
781
+ """
782
+ # NOTE: `blocks` comes from kv_cache_manager.get_blocks(request_id),
783
+ # which returns ALL blocks for the request (not just newly allocated).
784
+ # This function may be called twice for async-load requests:
785
+ # 1st call: blocks = initial allocation (APC + fresh)
786
+ # 2nd call: blocks = all blocks
787
+ # (initial + newly allocated for remaining tokens)
788
+ # We must only append the NEW blocks beyond what's already tracked
789
+ # to avoid duplication, which would corrupt the store path's block indexing.
790
+ tracker = self._get_request_tracker(request.request_id)
791
+ block_ids = reformat_block_ids(blocks.get_block_ids())
792
+
793
+ # Only append blocks beyond what's already tracked
794
+ existing_count = len(tracker.allocated_block_ids)
795
+ new_block_ids = block_ids[existing_count:]
796
+ if new_block_ids:
797
+ tracker.append_block_ids(new_block_ids)
798
+
799
+ # Update the state of the tracker
800
+ condition = tracker.needs_retrieve()
801
+ if tracker.state == LMCacheMPRequestState.PREFETCHING:
802
+ # If need to retrieve, change to WAITING_FOR_LOAD
803
+ # Otherwise, change to READY
804
+ tracker.state = (
805
+ LMCacheMPRequestState.WAITING_FOR_LOAD
806
+ if condition
807
+ else LMCacheMPRequestState.READY
808
+ )
809
+ # Clean up lookup future in scheduler adapter
810
+ self.scheduler_adapter.cleanup_lookup_result(request.request_id)
811
+
812
+ # Free locks on chunks that vLLM already computed and won't
813
+ # retrieve from LMCache.
814
+ if tracker.num_lmcache_hit_blocks > 0:
815
+ if not condition:
816
+ # No retrieve needed — free ALL locked chunks
817
+ free_end = tracker.num_lmcache_hit_blocks * self.vllm_block_size
818
+ else:
819
+ # Note(Roy): Boundary misalignment between vLLM blocks and LMCache
820
+ # blocks is handled in free_lookup_locks. It makes sure that if
821
+ # the last vLLM computed block ends in the middle of a LMCache
822
+ # block, the end LMCache block is not freed (i.e., floor division)
823
+ # since it will still be needed by vLLM and such block's lock will
824
+ # be freed by vLLM's retrieve.
825
+ free_end = tracker.num_vllm_hit_blocks * self.vllm_block_size
826
+
827
+ if free_end > 0:
828
+ self.scheduler_adapter.free_lookup_locks(
829
+ token_ids=list(tracker.all_token_ids),
830
+ start=0,
831
+ end=free_end,
832
+ request_id=request.request_id,
833
+ )
834
+ logger.debug(
835
+ "Free locks of tokens %d-%d since it is cached by vLLM.",
836
+ 0,
837
+ free_end,
838
+ )
839
+
840
+ def build_connector_meta(
841
+ self, scheduler_output: SchedulerOutput
842
+ ) -> KVConnectorMetadata:
843
+ """
844
+ Build the connector metadata for this step.
845
+
846
+ This function should NOT modify fields in the scheduler_output.
847
+ Also, calling this function will reset the state of the connector.
848
+
849
+ Args:
850
+ scheduler_output (SchedulerOutput): the scheduler output object.
851
+ """
852
+ metadata = LMCacheMPConnectorMetadata()
853
+
854
+ self._process_retrieve_requests(metadata)
855
+ self._process_new_requests(scheduler_output, metadata)
856
+ self._process_cached_requests(scheduler_output, metadata)
857
+
858
+ if len(metadata) > 0:
859
+ logger.debug("Final connector metadata: %s", metadata)
860
+
861
+ return metadata
862
+
863
+ def update_connector_output(self, connector_output: KVConnectorOutput):
864
+ """
865
+ Update KVConnector state from worker-side connectors output.
866
+
867
+ Args:
868
+ connector_output (KVConnectorOutput): the worker-side
869
+ connectors output.
870
+ """
871
+ return
872
+
873
+ def request_finished(
874
+ self,
875
+ request: "Request",
876
+ block_ids: list[int],
877
+ ) -> tuple[bool, dict[str, Any] | None]:
878
+ """
879
+ Called exactly once when a request has finished, before its blocks are
880
+ freed.
881
+
882
+ The connector may assumes responsibility for freeing the blocks
883
+ asynchronously by returning True.
884
+
885
+ Returns:
886
+ True if the request is being saved/sent asynchronously and blocks
887
+ should not be freed until the request_id is returned from
888
+ get_finished().
889
+ Optional KVTransferParams to be included in the request outputs
890
+ returned by the engine.
891
+ """
892
+ # Clean up request tracker to prevent memory leak
893
+ self._cleanup_request_tracker(request.request_id)
894
+ # Notify LMCache to end the session for this request
895
+ self.scheduler_adapter.end_session(request.request_id)
896
+
897
+ return True, None
898
+
899
+ def take_events(self) -> Iterable["KVCacheEvent"]:
900
+ """
901
+ Take the KV cache events from the connector.
902
+
903
+ Yields:
904
+ New KV cache events since the last call.
905
+ """
906
+ return ()
907
+
908
+ @classmethod
909
+ def get_required_kvcache_layout(cls, vllm_config: "VllmConfig") -> str | None:
910
+ """
911
+ Get the required KV cache layout for this connector.
912
+ Args:
913
+ vllm_config (VllmConfig): the vllm config.
914
+
915
+ Returns:
916
+ str: the required KV cache layout. e.g. HND, or NHD.
917
+ None if the connector does not require a specific layout.
918
+ """
919
+
920
+ if cls is KVConnectorBase_V1:
921
+ raise TypeError(
922
+ "get_required_kvcache_layout should not be called "
923
+ "on the abstract base class"
924
+ )
925
+ return None
926
+
927
+ def get_finished_count(self) -> int | None:
928
+ """
929
+ Get the count of requests expected to complete send/receive operations
930
+ via this connector. This method is used to initialize the
931
+ KVOutputAggregator, overwriting the default world_size.
932
+
933
+ Returns:
934
+ int: expected sending or receiving completion count.
935
+ """
936
+ return None
937
+
938
+ @classmethod
939
+ def build_kv_connector_stats(
940
+ cls, data: dict[str, Any] | None = None
941
+ ) -> "KVConnectorStats | None":
942
+ """
943
+ KVConnectorStats resolution method. This method allows dynamically
944
+ registered connectors to return their own KVConnectorStats object,
945
+ which can implement custom aggregation logic on the data dict.
946
+ """
947
+ return None
948
+
949
+ @classmethod
950
+ def build_prom_metrics(
951
+ cls,
952
+ vllm_config: "VllmConfig",
953
+ metric_types: dict[type["PromMetric"], type["PromMetricT"]],
954
+ labelnames: list[str],
955
+ per_engine_labelvalues: dict[int, list[object]],
956
+ ) -> "KVConnectorPromMetrics | None":
957
+ """
958
+ Create a KVConnectorPromMetrics subclass which should register
959
+ per-connector Prometheus metrics and implement observe() to
960
+ expose connector transfer stats via Prometheus.
961
+ """
962
+ return None
963
+
964
+ ##############################
965
+ # Helper functions
966
+ ##############################
967
+ def _process_retrieve_requests(
968
+ self,
969
+ metadata: LMCacheMPConnectorMetadata,
970
+ ) -> None:
971
+ blocks_per_chunk = self.scheduler_adapter.num_blocks_per_chunk()
972
+
973
+ for request_tracker in self.request_trackers.values():
974
+ if request_tracker.state != LMCacheMPRequestState.WAITING_FOR_LOAD:
975
+ continue
976
+ r_metadata = LMCacheMPRequestMetadata.GetRetrieveMetadata(
977
+ request_tracker,
978
+ blocks_per_chunk,
979
+ vllm_block_size=self.vllm_block_size,
980
+ )
981
+ if r_metadata is not None:
982
+ metadata.add_request_metadata(r_metadata)
983
+ request_tracker.state = LMCacheMPRequestState.READY
984
+
985
+ def _process_new_requests(
986
+ self,
987
+ scheduler_output: SchedulerOutput,
988
+ metadata: LMCacheMPConnectorMetadata,
989
+ ) -> None:
990
+ blocks_per_chunk = self.scheduler_adapter.num_blocks_per_chunk()
991
+
992
+ for new_request in scheduler_output.scheduled_new_reqs:
993
+ request_tracker = self._get_request_tracker(new_request.req_id)
994
+
995
+ num_new_tokens = scheduler_output.num_scheduled_tokens[new_request.req_id]
996
+ request_tracker.increase_num_scheduled_tokens(num_new_tokens)
997
+
998
+ r_meta = LMCacheMPRequestMetadata.GetStoreMetadata(
999
+ request_tracker, blocks_per_chunk, self.vllm_block_size
1000
+ )
1001
+ if r_meta is not None:
1002
+ metadata.add_request_metadata(r_meta)
1003
+
1004
+ def _process_cached_requests(
1005
+ self,
1006
+ scheduler_output: SchedulerOutput,
1007
+ metadata: LMCacheMPConnectorMetadata,
1008
+ ) -> None:
1009
+ blocks_per_chunk = self.scheduler_adapter.num_blocks_per_chunk()
1010
+
1011
+ cached_reqs = scheduler_output.scheduled_cached_reqs
1012
+ for idx, request_id in enumerate(cached_reqs.req_ids):
1013
+ request_tracker = self._get_request_tracker(request_id)
1014
+
1015
+ # Update block ids
1016
+ new_block_ids = reformat_block_ids(cached_reqs.new_block_ids[idx])
1017
+ if request_id not in cached_reqs.resumed_req_ids:
1018
+ request_tracker.append_block_ids(new_block_ids)
1019
+
1020
+ # Use the incremental num_scheduled_tokens to
1021
+ # stay consistent with _process_new_requests.
1022
+ num_new_tokens = scheduler_output.num_scheduled_tokens[request_id]
1023
+ request_tracker.increase_num_scheduled_tokens(num_new_tokens)
1024
+
1025
+ r_meta = LMCacheMPRequestMetadata.GetStoreMetadata(
1026
+ request_tracker, blocks_per_chunk, self.vllm_block_size
1027
+ )
1028
+
1029
+ if r_meta is not None:
1030
+ metadata.add_request_metadata(r_meta)
1031
+
1032
+ def _get_request_tracker(self, request_id: str) -> LMCacheMPRequestTracker:
1033
+ assert request_id in self.request_trackers, (
1034
+ f"Request tracker for request_id {request_id} not found. "
1035
+ )
1036
+ return self.request_trackers[request_id]
1037
+
1038
+ def _get_or_create_request_tracker(
1039
+ self, request: "Request"
1040
+ ) -> LMCacheMPRequestTracker:
1041
+ request_id = request.request_id
1042
+ # Remove the old trackers that is created before the preemption
1043
+ if (
1044
+ request.status == RequestStatus.PREEMPTED
1045
+ and request_id in self.request_trackers
1046
+ ):
1047
+ tracker = self.request_trackers[request_id]
1048
+
1049
+ # NOTE: since this function may be called multiple times
1050
+ # for a single request (because get_num_new_matched_tokens
1051
+ # may be called multiple times) for the same request, we
1052
+ # will only do the remove if the tracker is not in the "fresh"
1053
+ # state, i.e., PREFETCHING
1054
+ if tracker.state != LMCacheMPRequestState.PREFETCHING:
1055
+ self.request_trackers.pop(request_id)
1056
+
1057
+ if request_id not in self.request_trackers:
1058
+ new_tracker = LMCacheMPRequestTracker(request)
1059
+ self.request_trackers[request_id] = new_tracker
1060
+ return self.request_trackers[request_id]
1061
+
1062
+ def _cleanup_request_tracker(self, request_id: str) -> None:
1063
+ """
1064
+ Clean up request tracker and associated lookup future for a request.
1065
+ This should be called when a request is finished to prevent memory leak.
1066
+ """
1067
+ # Clean up request tracker
1068
+ if self.request_trackers.pop(request_id, None):
1069
+ logger.debug(
1070
+ "[KVConnector] Cleaned up request_tracker for request %s",
1071
+ request_id,
1072
+ )