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,734 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # Standard
4
+ from concurrent.futures import Future
5
+ from typing import Any, Callable, List, Optional, Sequence, Union
6
+ import asyncio
7
+ import threading
8
+ import time
9
+
10
+ # Third Party
11
+ from maru import MaruConfig, MaruHandler
12
+ from maru_lmcache import CxlMemoryAdapter
13
+ import torch
14
+
15
+ # First Party
16
+ from lmcache.integration.vllm.utils import get_size_bytes
17
+ from lmcache.logging import init_logger
18
+ from lmcache.utils import CacheEngineKey
19
+ from lmcache.v1.config import LMCacheEngineConfig
20
+ from lmcache.v1.memory_management import (
21
+ MemoryAllocatorInterface,
22
+ MemoryFormat,
23
+ MemoryObj,
24
+ )
25
+ from lmcache.v1.metadata import LMCacheMetadata
26
+ from lmcache.v1.storage_backend.abstract_backend import AllocatorBackendInterface
27
+
28
+ logger = init_logger(__name__)
29
+
30
+
31
+ class MaruBackend(AllocatorBackendInterface):
32
+ """Maru CXL shared memory storage backend.
33
+
34
+ Implements AllocatorBackendInterface with its own CxlMemoryAdapter.
35
+ No LocalCPUBackend needed — data lives directly in CXL mmap memory.
36
+
37
+ Put is async (Future): metadata registration via RPC.
38
+ Get is sync: CXL memory direct read (no network I/O).
39
+
40
+ Args:
41
+ config: LMCache engine configuration. Must have maru_path set.
42
+ metadata: LMCache engine metadata.
43
+ loop: asyncio event loop for async put tasks.
44
+ dst_device: Target device string (unused for CXL, kept for interface).
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ config: LMCacheEngineConfig,
50
+ metadata: LMCacheMetadata,
51
+ loop: asyncio.AbstractEventLoop,
52
+ dst_device: str = "cuda",
53
+ ):
54
+ super().__init__(dst_device=dst_device)
55
+
56
+ if config.use_layerwise:
57
+ raise NotImplementedError(
58
+ "MaruBackend does not yet support layerwise KV cache."
59
+ )
60
+
61
+ # 1. Config
62
+ self.config = config
63
+ self.loop = loop
64
+
65
+ self._full_chunk_size_bytes: int = get_size_bytes(
66
+ metadata.get_shapes(), metadata.get_dtypes()
67
+ )
68
+ assert self._full_chunk_size_bytes % metadata.chunk_size == 0
69
+ self._single_token_size: int = (
70
+ self._full_chunk_size_bytes // metadata.chunk_size
71
+ )
72
+
73
+ self._mla_worker_id_as0_mode: bool = (
74
+ config.get_extra_config_value(
75
+ "remote_enable_mla_worker_id_as0", metadata.use_mla
76
+ )
77
+ and metadata.use_mla
78
+ and metadata.world_size > 1
79
+ and metadata.worker_id != 0
80
+ )
81
+
82
+ # 2. Handler
83
+ self._handler = self._create_handler(config)
84
+
85
+ # 3. Allocator
86
+ self.memory_allocator = self.initialize_allocator(config, metadata)
87
+
88
+ # 4. State
89
+ self.put_lock = threading.Lock()
90
+ self.put_tasks: set[CacheEngineKey] = set()
91
+
92
+ def __str__(self) -> str:
93
+ return self.__class__.__name__
94
+
95
+ @staticmethod
96
+ def _pool_size_gb_to_bytes(size_gb: float) -> int:
97
+ """Convert pool size in GB to bytes."""
98
+ return int(size_gb * 1024**3)
99
+
100
+ # =========================================================================
101
+ # Initialization helpers
102
+ # =========================================================================
103
+
104
+ def _create_handler(
105
+ self,
106
+ config: LMCacheEngineConfig,
107
+ ) -> "MaruHandler":
108
+ """Create and connect a MaruHandler.
109
+
110
+ Args:
111
+ config: LMCache engine configuration.
112
+
113
+ Returns:
114
+ Connected MaruHandler instance.
115
+
116
+ Raises:
117
+ RuntimeError: If MaruHandler connection fails.
118
+ """
119
+ assert config.maru_path is not None, "maru_path must be set for MaruBackend"
120
+
121
+ # Convert maru:// scheme to tcp:// for ZMQ
122
+ server_url = config.maru_path
123
+ if server_url.startswith("maru://"):
124
+ server_url = "tcp://" + server_url[len("maru://") :]
125
+
126
+ extra = config.extra_config or {}
127
+ maru_config = MaruConfig(
128
+ server_url=server_url,
129
+ instance_id=extra.get("maru_instance_id"),
130
+ pool_size=self._pool_size_gb_to_bytes(config.maru_pool_size),
131
+ chunk_size_bytes=self._full_chunk_size_bytes,
132
+ auto_connect=False,
133
+ timeout_ms=extra.get("maru_timeout_ms", 5000),
134
+ use_async_rpc=extra.get("maru_use_async_rpc", True),
135
+ max_inflight=extra.get("maru_max_inflight", 64),
136
+ eager_map=extra.get("maru_eager_map", True),
137
+ )
138
+
139
+ handler = MaruHandler(maru_config)
140
+ if not handler.connect():
141
+ raise RuntimeError(f"Failed to connect MaruHandler to {config.maru_path}")
142
+ logger.debug("[Maru] Connected to %s", config.maru_path)
143
+ return handler
144
+
145
+ # =========================================================================
146
+ # AllocatorBackendInterface
147
+ # =========================================================================
148
+
149
+ def initialize_allocator(
150
+ self, config: LMCacheEngineConfig, metadata: LMCacheMetadata
151
+ ) -> MemoryAllocatorInterface:
152
+ """Create CxlMemoryAdapter backed by the connected handler.
153
+
154
+ Args:
155
+ config: LMCache engine configuration.
156
+ metadata: LMCache engine metadata.
157
+
158
+ Returns:
159
+ CxlMemoryAdapter instance.
160
+ """
161
+ shapes = metadata.get_shapes()
162
+ dtypes = metadata.get_dtypes()
163
+ fmt = MemoryFormat.KV_MLA_FMT if metadata.use_mla else MemoryFormat.KV_2LTD
164
+ chunk_size = self._handler.get_chunk_size()
165
+
166
+ return CxlMemoryAdapter(
167
+ handler=self._handler,
168
+ shapes=shapes,
169
+ dtypes=dtypes,
170
+ fmt=fmt,
171
+ chunk_size=chunk_size,
172
+ )
173
+
174
+ def get_memory_allocator(self) -> MemoryAllocatorInterface:
175
+ """Returns the underlying CxlMemoryAdapter."""
176
+ return self.memory_allocator
177
+
178
+ def get_allocator_backend(self) -> "MaruBackend":
179
+ """Returns self as the allocator backend."""
180
+ return self
181
+
182
+ def allocate(
183
+ self,
184
+ shapes: Union[torch.Size, list[torch.Size]],
185
+ dtypes: Union[torch.dtype, list[torch.dtype]],
186
+ fmt: MemoryFormat = MemoryFormat.KV_2LTD,
187
+ eviction: bool = True,
188
+ busy_loop: bool = True,
189
+ ) -> Optional[MemoryObj]:
190
+ """Allocate CXL-backed memory via CxlMemoryAdapter.
191
+
192
+ Args:
193
+ shapes: Tensor shape(s).
194
+ dtypes: Tensor dtype(s).
195
+ fmt: Memory format.
196
+ eviction: Unused.
197
+ busy_loop: Unused.
198
+
199
+ Returns:
200
+ MemoryObj backed by CXL memory, or None on failure.
201
+ """
202
+ obj = self.memory_allocator.allocate(shapes, dtypes, fmt)
203
+ if obj is not None:
204
+ logger.debug(
205
+ "[Maru] allocate rid=%d pid=%d",
206
+ *CxlMemoryAdapter.decode_address(obj.metadata.address),
207
+ )
208
+ else:
209
+ logger.debug("[Maru] allocate failed shapes=%s dtypes=%s", shapes, dtypes)
210
+ return obj
211
+
212
+ def batched_allocate(
213
+ self,
214
+ shapes: Union[torch.Size, list[torch.Size]],
215
+ dtypes: Union[torch.dtype, list[torch.dtype]],
216
+ batch_size: int,
217
+ fmt: MemoryFormat = MemoryFormat.KV_2LTD,
218
+ eviction: bool = True,
219
+ busy_loop: bool = True,
220
+ ) -> Optional[list[MemoryObj]]:
221
+ """Allocate multiple CXL-backed MemoryObjs.
222
+
223
+ Args:
224
+ shapes: Tensor shape(s) (same for each allocation).
225
+ dtypes: Tensor dtype(s) (same for each allocation).
226
+ batch_size: Number of allocations.
227
+ fmt: Memory format.
228
+ eviction: Unused.
229
+ busy_loop: Unused.
230
+
231
+ Returns:
232
+ List of MemoryObj, or None if any allocation fails.
233
+ """
234
+ return self.memory_allocator.batched_allocate(shapes, dtypes, batch_size, fmt)
235
+
236
+ # =========================================================================
237
+ # Put (async)
238
+ # =========================================================================
239
+
240
+ def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
241
+ """Check whether key is in ongoing put tasks.
242
+
243
+ Args:
244
+ key: The cache key.
245
+
246
+ Returns:
247
+ True if the key has a pending put task.
248
+ """
249
+ with self.put_lock:
250
+ return key in self.put_tasks
251
+
252
+ @staticmethod
253
+ def _create_immediate_empty_future() -> Future:
254
+ """Create a Future that is already resolved with None."""
255
+ f: Future = Future()
256
+ f.set_result(None)
257
+ return f
258
+
259
+ def submit_put_task(
260
+ self,
261
+ key: CacheEngineKey,
262
+ memory_obj: MemoryObj,
263
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
264
+ ) -> Future:
265
+ """Submit a put task to register KV metadata with MaruServer.
266
+
267
+ Data is already in CXL memory (zero-copy). This only registers
268
+ the key -> location metadata via RPC.
269
+
270
+ Args:
271
+ key: The cache key.
272
+ memory_obj: MemoryObj with data already written to CXL.
273
+ on_complete_callback: Optional callback after registration.
274
+
275
+ Returns:
276
+ Future that completes when metadata is registered.
277
+ """
278
+ # If MLA worker id as 0 mode is enabled, skip put tasks
279
+ if self._mla_worker_id_as0_mode:
280
+ return self._create_immediate_empty_future()
281
+
282
+ assert memory_obj.tensor is not None
283
+
284
+ # Keep CXL page alive: ref_count_down is only called on failure.
285
+ # On success the ref is retained so the CXL memory is not reclaimed.
286
+ memory_obj.ref_count_up()
287
+
288
+ with self.put_lock:
289
+ self.put_tasks.add(key)
290
+
291
+ future = asyncio.run_coroutine_threadsafe(
292
+ self._async_store(key, memory_obj, on_complete_callback),
293
+ self.loop,
294
+ )
295
+ return future
296
+
297
+ def batched_submit_put_task(
298
+ self,
299
+ keys: Sequence[CacheEngineKey],
300
+ memory_objs: List[MemoryObj],
301
+ transfer_spec: Any = None,
302
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
303
+ ) -> Union[List[Future], None]:
304
+ """Submit batched put tasks via single batch_store RPC.
305
+
306
+ Args:
307
+ keys: The cache keys.
308
+ memory_objs: MemoryObjs with data already in CXL.
309
+ transfer_spec: Unused.
310
+ on_complete_callback: Optional per-key callback.
311
+
312
+ Returns:
313
+ List containing a single Future for the entire batch.
314
+ """
315
+ # If MLA worker id as 0 mode is enabled, skip put tasks
316
+ if self._mla_worker_id_as0_mode:
317
+ return None
318
+
319
+ for memory_obj in memory_objs:
320
+ assert memory_obj.tensor is not None
321
+ memory_obj.ref_count_up()
322
+
323
+ with self.put_lock:
324
+ self.put_tasks.update(keys)
325
+
326
+ future = asyncio.run_coroutine_threadsafe(
327
+ self._async_batch_store(list(keys), memory_objs, on_complete_callback),
328
+ self.loop,
329
+ )
330
+ return [future]
331
+
332
+ async def _async_store(
333
+ self,
334
+ key: CacheEngineKey,
335
+ memory_obj: MemoryObj,
336
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
337
+ ) -> None:
338
+ """Register KV metadata with MaruServer (runs in event loop).
339
+
340
+ Uses CxlMemoryAdapter.create_store_handle() to extract
341
+ (region_id, page_index) from the MemoryObj's encoded address.
342
+
343
+ Args:
344
+ key: The cache key.
345
+ memory_obj: MemoryObj backed by CXL memory.
346
+ on_complete_callback: Optional callback after registration.
347
+ """
348
+ success = False
349
+ try:
350
+ allocator = self.memory_allocator
351
+ assert isinstance(allocator, CxlMemoryAdapter)
352
+ handle = allocator.create_store_handle(memory_obj)
353
+ key_str = key.to_string()
354
+
355
+ success = await asyncio.to_thread(self._handler.store, key_str, handle)
356
+
357
+ logger.debug(
358
+ "[Maru] store key=%s rid=%d pid=%d",
359
+ key,
360
+ handle.region_id,
361
+ handle.page_index,
362
+ )
363
+
364
+ except Exception as e:
365
+ logger.error("[Maru] store failed key=%s: %s", key, e)
366
+ raise
367
+ finally:
368
+ with self.put_lock:
369
+ self.put_tasks.discard(key)
370
+
371
+ if not success:
372
+ memory_obj.ref_count_down()
373
+
374
+ if success and on_complete_callback is not None:
375
+ try:
376
+ on_complete_callback(key)
377
+ except Exception as e:
378
+ logger.warning("on_complete_callback failed for key %s: %s", key, e)
379
+
380
+ async def _async_batch_store(
381
+ self,
382
+ keys: List[CacheEngineKey],
383
+ memory_objs: List[MemoryObj],
384
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
385
+ ) -> None:
386
+ """Register multiple KV metadata entries via single batch_store RPC."""
387
+ results: Optional[list[bool]] = None
388
+ try:
389
+ allocator = self.memory_allocator
390
+ assert isinstance(allocator, CxlMemoryAdapter)
391
+
392
+ key_strs = [k.to_string() for k in keys]
393
+ handles = [allocator.create_store_handle(m) for m in memory_objs]
394
+
395
+ results = await asyncio.to_thread(
396
+ self._handler.batch_store, key_strs, handles
397
+ )
398
+ if results is not None:
399
+ logger.debug("[Maru] batch_store %d/%d ok", sum(results), len(results))
400
+ except Exception as e:
401
+ logger.error("[Maru] batch_store failed: %s", e)
402
+ raise
403
+ finally:
404
+ with self.put_lock:
405
+ self.put_tasks.difference_update(keys)
406
+
407
+ # Release ref_count for failed stores
408
+ for i, memory_obj in enumerate(memory_objs):
409
+ succeeded = results is not None and i < len(results) and results[i]
410
+ if not succeeded:
411
+ memory_obj.ref_count_down()
412
+
413
+ if on_complete_callback is not None:
414
+ for i, key in enumerate(keys):
415
+ if results is not None and i < len(results) and results[i]:
416
+ try:
417
+ on_complete_callback(key)
418
+ except Exception as e:
419
+ logger.warning(
420
+ "on_complete_callback failed for key %s: %s",
421
+ key,
422
+ e,
423
+ )
424
+
425
+ # =========================================================================
426
+ # Get (sync)
427
+ # =========================================================================
428
+
429
+ def get_blocking(
430
+ self,
431
+ key: CacheEngineKey,
432
+ ) -> Optional[MemoryObj]:
433
+ """Blocking get: read KV cache directly from CXL memory.
434
+
435
+ Queries MaruServer for metadata, then returns a MemoryObj
436
+ via CxlMemoryAdapter.get_by_location().
437
+
438
+ Args:
439
+ key: The cache key.
440
+
441
+ Returns:
442
+ MemoryObj backed by CXL memory, or None if not found.
443
+ """
444
+ if self._mla_worker_id_as0_mode:
445
+ key = key.with_new_worker_id(0)
446
+
447
+ key_str = key.to_string()
448
+ mem_info = self._handler.retrieve(key_str)
449
+ if mem_info is None:
450
+ logger.debug("[Maru] get_blocking miss key=%s", key)
451
+ return None
452
+
453
+ allocator = self.memory_allocator
454
+ assert isinstance(allocator, CxlMemoryAdapter)
455
+
456
+ memory_obj = allocator.get_by_location(
457
+ region_id=mem_info.region_id,
458
+ page_index=mem_info.page_index,
459
+ actual_size=len(mem_info.view),
460
+ single_token_size=self._single_token_size,
461
+ )
462
+ if memory_obj is None:
463
+ logger.debug(
464
+ "[Maru] get_blocking pool miss rid=%d pid=%d",
465
+ mem_info.region_id,
466
+ mem_info.page_index,
467
+ )
468
+ return None
469
+
470
+ memory_obj.ref_count_up()
471
+
472
+ logger.debug(
473
+ "[Maru] get_blocking rid=%d pid=%d size=%d",
474
+ mem_info.region_id,
475
+ mem_info.page_index,
476
+ len(mem_info.view),
477
+ )
478
+ return memory_obj
479
+
480
+ def batched_get_blocking(
481
+ self,
482
+ keys: List[CacheEngineKey],
483
+ ) -> List[Optional[MemoryObj]]:
484
+ """Blocking batched get via single batch_retrieve RPC.
485
+
486
+ Args:
487
+ keys: The cache keys.
488
+
489
+ Returns:
490
+ List of MemoryObj (None for misses).
491
+ """
492
+ if self._mla_worker_id_as0_mode:
493
+ keys = [k.with_new_worker_id(0) for k in keys]
494
+
495
+ key_strs = [k.to_string() for k in keys]
496
+ mem_infos = self._handler.batch_retrieve(key_strs)
497
+
498
+ allocator = self.memory_allocator
499
+ assert isinstance(allocator, CxlMemoryAdapter)
500
+
501
+ results: List[Optional[MemoryObj]] = []
502
+ for mem_info in mem_infos:
503
+ if mem_info is None:
504
+ results.append(None)
505
+ continue
506
+ memory_obj = allocator.get_by_location(
507
+ region_id=mem_info.region_id,
508
+ page_index=mem_info.page_index,
509
+ actual_size=len(mem_info.view),
510
+ single_token_size=self._single_token_size,
511
+ )
512
+ if memory_obj is None:
513
+ results.append(None)
514
+ continue
515
+ memory_obj.ref_count_up()
516
+ results.append(memory_obj)
517
+
518
+ hits = sum(1 for r in results if r is not None)
519
+ logger.debug("[Maru] batch_retrieve %d/%d hits", hits, len(results))
520
+ return results
521
+
522
+ # =========================================================================
523
+ # Async lookup API (used by StorageManager.async_lookup_and_prefetch)
524
+ # =========================================================================
525
+
526
+ async def batched_async_contains(
527
+ self,
528
+ lookup_id: str,
529
+ keys: List[CacheEngineKey],
530
+ pin: bool = False,
531
+ ) -> int:
532
+ """Check how many prefix keys exist via single batch_exists RPC.
533
+
534
+ Returns the count of contiguous keys starting from index 0
535
+ that exist. Stops at first miss.
536
+
537
+ Args:
538
+ lookup_id: Unique request identifier.
539
+ keys: Keys to check in prefix order.
540
+ pin: If True, atomically check and pin via batch_pin RPC.
541
+
542
+ Returns:
543
+ Number of prefix-contiguous keys that exist.
544
+ """
545
+ return await asyncio.to_thread(self.batched_contains, keys, pin)
546
+
547
+ async def batched_get_non_blocking(
548
+ self,
549
+ lookup_id: str,
550
+ keys: list[CacheEngineKey],
551
+ transfer_spec: Any = None,
552
+ ) -> list[MemoryObj]:
553
+ """Non-blocking batched get via single batch_retrieve RPC.
554
+
555
+ Uses handler.batch_retrieve() for a single RPC call, then
556
+ resolves each MemoryInfo to a MemoryObj via CxlMemoryAdapter.
557
+ Stops at first miss and returns the prefix.
558
+
559
+ Args:
560
+ lookup_id: Unique request identifier.
561
+ keys: Keys to retrieve (already confirmed by contains).
562
+ transfer_spec: Unused.
563
+
564
+ Returns:
565
+ List of MemoryObjs backed by CXL memory.
566
+ """
567
+
568
+ def _batch_get() -> list[MemoryObj]:
569
+ if self._mla_worker_id_as0_mode:
570
+ actual_keys = [k.with_new_worker_id(0) for k in keys]
571
+ else:
572
+ actual_keys = list(keys)
573
+
574
+ key_strs = [k.to_string() for k in actual_keys]
575
+ mem_infos = self._handler.batch_retrieve(key_strs)
576
+
577
+ allocator = self.memory_allocator
578
+ assert isinstance(allocator, CxlMemoryAdapter)
579
+
580
+ results: list[MemoryObj] = []
581
+ for mem_info in mem_infos:
582
+ if mem_info is None:
583
+ break
584
+ memory_obj = allocator.get_by_location(
585
+ region_id=mem_info.region_id,
586
+ page_index=mem_info.page_index,
587
+ actual_size=len(mem_info.view),
588
+ single_token_size=self._single_token_size,
589
+ )
590
+ if memory_obj is None:
591
+ break
592
+ memory_obj.ref_count_up()
593
+ memory_obj.pin()
594
+ results.append(memory_obj)
595
+
596
+ logger.debug(
597
+ "[Maru] batch_get_non_blocking %d/%d hits", len(results), len(keys)
598
+ )
599
+ return results
600
+
601
+ return await asyncio.to_thread(_batch_get)
602
+
603
+ # =========================================================================
604
+ # Contains / Pin / Unpin / Remove
605
+ # =========================================================================
606
+
607
+ def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
608
+ """Check if key exists on MaruServer.
609
+
610
+ Args:
611
+ key: The cache key.
612
+ pin: If True, atomically check existence and pin the entry
613
+ to protect it from eviction.
614
+
615
+ Returns:
616
+ True if key exists.
617
+ """
618
+ if self._mla_worker_id_as0_mode:
619
+ key = key.with_new_worker_id(0)
620
+
621
+ key_str = key.to_string()
622
+ if pin:
623
+ return self._handler.pin(key_str)
624
+ return self._handler.exists(key_str)
625
+
626
+ def batched_contains(
627
+ self,
628
+ keys: List[CacheEngineKey],
629
+ pin: bool = False,
630
+ ) -> int:
631
+ """Check how many prefix keys exist via single batch_exists RPC.
632
+
633
+ Args:
634
+ keys: Keys to check in prefix order.
635
+ pin: If True, atomically check and pin via
636
+ batch_pin RPC.
637
+
638
+ Returns:
639
+ Number of prefix-contiguous keys that exist.
640
+ """
641
+ if self._mla_worker_id_as0_mode:
642
+ keys = [k.with_new_worker_id(0) for k in keys]
643
+
644
+ key_strs = [k.to_string() for k in keys]
645
+ if pin:
646
+ results = self._handler.batch_pin(key_strs)
647
+ else:
648
+ results = self._handler.batch_exists(key_strs)
649
+ num_hit = 0
650
+ for exists in results:
651
+ if not exists:
652
+ break
653
+ num_hit += 1
654
+ return num_hit
655
+
656
+ def pin(self, key: CacheEngineKey) -> bool:
657
+ """Pin a key to prevent eviction on MaruServer.
658
+
659
+ Increments the server-side pin_count.
660
+
661
+ Args:
662
+ key: The cache key.
663
+
664
+ Returns:
665
+ True if pinned successfully.
666
+ """
667
+ if self._mla_worker_id_as0_mode:
668
+ key = key.with_new_worker_id(0)
669
+ return self._handler.pin(key.to_string())
670
+
671
+ def unpin(self, key: CacheEngineKey) -> bool:
672
+ """Unpin a key to allow eviction on MaruServer.
673
+
674
+ Decrements the server-side pin_count. When pin_count reaches 0,
675
+ the entry becomes eligible for eviction.
676
+
677
+ Args:
678
+ key: The cache key.
679
+
680
+ Returns:
681
+ True if unpinned successfully.
682
+ """
683
+ if self._mla_worker_id_as0_mode:
684
+ key = key.with_new_worker_id(0)
685
+ return self._handler.unpin(key.to_string())
686
+
687
+ def batched_unpin(self, keys: List[CacheEngineKey]) -> None:
688
+ """Batch-unpin keys via single RPC.
689
+
690
+ Decrements server-side pin_count for each key. When pin_count
691
+ reaches 0, the entry becomes eligible for eviction.
692
+
693
+ Args:
694
+ keys: The cache keys to unpin.
695
+ """
696
+ if not keys:
697
+ return
698
+ if self._mla_worker_id_as0_mode:
699
+ keys = [k.with_new_worker_id(0) for k in keys]
700
+ key_strs = [k.to_string() for k in keys]
701
+ self._handler.batch_unpin(key_strs)
702
+
703
+ def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
704
+ """Remove a key from MaruServer.
705
+
706
+ Args:
707
+ key: The cache key.
708
+ force: Whether to force removal.
709
+
710
+ Returns:
711
+ True if removed successfully.
712
+ """
713
+ if self._mla_worker_id_as0_mode:
714
+ key = key.with_new_worker_id(0)
715
+ key_str = key.to_string()
716
+ result = self._handler.delete(key_str)
717
+ logger.debug("[Maru] remove key=%s success=%s", key, result)
718
+ return result
719
+
720
+ # =========================================================================
721
+ # Lifecycle
722
+ # =========================================================================
723
+
724
+ def close(self) -> None:
725
+ """Close the backend and underlying MaruHandler."""
726
+ while True:
727
+ with self.put_lock:
728
+ if not self.put_tasks:
729
+ break
730
+ time.sleep(0.1)
731
+
732
+ self.memory_allocator.close()
733
+ self._handler.close()
734
+ logger.info("MaruBackend closed.")