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,627 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ ValkeyConnector — high-throughput Valkey connector using the GLIDE sync
4
+ client (standalone or cluster) with a ThreadPoolExecutor and per-thread clients.
5
+
6
+ This replaces the legacy async ValkeyConnector / ValkeyClusterConnector
7
+ that used the async GLIDE client with 2-key storage. The implementation
8
+ is shared with ``sync_valkey_connector.SyncValkeyConnector`` (which
9
+ registers on the ``valkey-sync://`` scheme as a backward-compat alias).
10
+
11
+ Design choices:
12
+ - N worker threads, each with its own GLIDE sync client
13
+ (``GlideClient`` in standalone mode, ``GlideClusterClient`` in cluster mode)
14
+ via ``threading.local()``, enabling true parallel I/O when the GIL is
15
+ released during FFI calls.
16
+ - Direct ``memoryview`` access to pinned CPU memory — no shared-memory
17
+ arena or cross-process copies needed since threads share the parent's
18
+ address space.
19
+ - Single-key storage (like RESPConnector) to halve Valkey round-trips
20
+ compared to the legacy 2-key metadata/kv_bytes split.
21
+ - Priority scheduling via ``AsyncPQExecutor`` (PEEK > PREFETCH > GET > PUT)
22
+ ensures latency-sensitive lookups are not delayed behind bulk writes,
23
+ matching the priority scheme used by ``RESPConnector``.
24
+
25
+ Migration notes from the old ValkeyConnector:
26
+ - Standalone mode (default) uses ``GlideClient`` and supports ``database_id``.
27
+ - Cluster mode (``valkey_mode: "cluster"``) uses ``GlideClusterClient`` which
28
+ auto-discovers cluster topology from a single seed node.
29
+
30
+ Requires ``valkey-glide`` with PRs #5492 (zero-copy SET) and #5493 (buffer GET).
31
+ """
32
+
33
+ # Standard
34
+ from concurrent.futures import Future, ThreadPoolExecutor
35
+ from enum import IntEnum, auto
36
+ from typing import List, Optional
37
+ import asyncio
38
+ import inspect
39
+ import threading
40
+
41
+ # First Party
42
+ from lmcache.logging import init_logger
43
+ from lmcache.utils import CacheEngineKey
44
+ from lmcache.v1.memory_management import MemoryObj
45
+ from lmcache.v1.storage_backend.connector.base_connector import RemoteConnector
46
+ from lmcache.v1.storage_backend.job_executor.pq_executor import AsyncPQExecutor
47
+ from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
48
+
49
+ logger = init_logger(__name__)
50
+
51
+ #: Default request timeout (seconds).
52
+ DEFAULT_REQUEST_TIMEOUT_SECS: float = 5.0
53
+ #: Default connection timeout (seconds).
54
+ DEFAULT_CONNECTION_TIMEOUT_SECS: float = 10.0
55
+
56
+
57
+ class Priorities(IntEnum):
58
+ """Operation priorities for the ``AsyncPQExecutor``.
59
+
60
+ Lower numeric value = higher priority. Matches the scheme used by
61
+ ``RESPConnector`` so that exists/peek checks run before bulk writes.
62
+ """
63
+
64
+ PEEK = auto()
65
+ PREFETCH = auto()
66
+ GET = auto()
67
+ PUT = auto()
68
+
69
+
70
+ class _ThreadWorkerPool:
71
+ """Manages a pool of threads, each with its own GLIDE sync client.
72
+
73
+ Each thread gets an independent GLIDE sync client
74
+ (``GlideClient`` or ``GlideClusterClient``) via
75
+ ``threading.local()``, enabling true parallel I/O when the GIL is
76
+ released during FFI calls.
77
+
78
+ Args:
79
+ host: Valkey server hostname.
80
+ port: Valkey server port.
81
+ num_workers: Number of worker threads.
82
+ username: Valkey authentication username.
83
+ password: Valkey authentication password.
84
+ request_timeout: Timeout in seconds for GLIDE requests and
85
+ Future.result() calls.
86
+ connection_timeout: Timeout in seconds for initial GLIDE client
87
+ connections and thread pool warmup.
88
+ tls_enable: Whether to use TLS for Valkey connections.
89
+ cluster_mode: If True, use GlideClusterClient; else GlideClient.
90
+ database_id: Database ID for standalone mode (ignored in cluster).
91
+ """
92
+
93
+ def __init__(
94
+ self,
95
+ host: str,
96
+ port: int,
97
+ num_workers: int,
98
+ username: str,
99
+ password: str,
100
+ request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECS,
101
+ connection_timeout: float = DEFAULT_CONNECTION_TIMEOUT_SECS,
102
+ tls_enable: bool = False,
103
+ cluster_mode: bool = False,
104
+ database_id: Optional[int] = None,
105
+ ):
106
+ self.num_workers = num_workers
107
+ self._host = host
108
+ self._port = port
109
+ self._username = username
110
+ self._password = password
111
+ self._request_timeout = request_timeout
112
+ self._request_timeout_ms = int(request_timeout * 1000)
113
+ self._connection_timeout_ms = int(connection_timeout * 1000)
114
+ self._tls_enable = tls_enable
115
+ self._cluster_mode = cluster_mode
116
+ self._database_id = database_id
117
+ self._local = threading.local()
118
+ self._has_buffer_get: Optional[bool] = None
119
+
120
+ self._executor = ThreadPoolExecutor(
121
+ max_workers=num_workers,
122
+ thread_name_prefix="valkey",
123
+ )
124
+ # Warm up: create a client on each thread
125
+ futs = [self._executor.submit(self._get_client) for _ in range(num_workers)]
126
+ for f in futs:
127
+ f.result(timeout=connection_timeout)
128
+ mode_str = "cluster" if cluster_mode else "standalone"
129
+ logger.info(
130
+ "Valkey thread pool: %d threads, mode=%s, per-thread clients, "
131
+ "buffer_get=%s",
132
+ num_workers,
133
+ mode_str,
134
+ self._has_buffer_get,
135
+ )
136
+
137
+ def _get_client(self): # type: ignore[no-untyped-def]
138
+ """Get or create the per-thread GLIDE sync client.
139
+
140
+ Creates a ``GlideClusterClient`` (cluster mode) or ``GlideClient``
141
+ (standalone mode) depending on the ``cluster_mode`` flag.
142
+ """
143
+ # Third Party
144
+ import glide_sync # type: ignore[import-untyped]
145
+
146
+ client = getattr(self._local, "client", None)
147
+ if client is not None:
148
+ return client
149
+
150
+ credentials = None
151
+ if self._username or self._password:
152
+ credentials = glide_sync.ServerCredentials(self._username, self._password)
153
+
154
+ address = glide_sync.NodeAddress(self._host, self._port)
155
+
156
+ if self._cluster_mode:
157
+ advanced = glide_sync.AdvancedGlideClusterClientConfiguration(
158
+ connection_timeout=self._connection_timeout_ms,
159
+ )
160
+ config_kwargs: dict = {
161
+ "addresses": [address],
162
+ "request_timeout": self._request_timeout_ms,
163
+ "use_tls": self._tls_enable,
164
+ "advanced_config": advanced,
165
+ }
166
+ if credentials is not None:
167
+ config_kwargs["credentials"] = credentials
168
+ config = glide_sync.GlideClusterClientConfiguration(**config_kwargs)
169
+ client = glide_sync.GlideClusterClient.create(config)
170
+ else:
171
+ # Standalone mode — supports database_id and advanced config
172
+ advanced = glide_sync.AdvancedGlideClientConfiguration(
173
+ connection_timeout=self._connection_timeout_ms,
174
+ )
175
+ config_kwargs = {
176
+ "addresses": [address],
177
+ "request_timeout": self._request_timeout_ms,
178
+ "use_tls": self._tls_enable,
179
+ "advanced_config": advanced,
180
+ }
181
+ if credentials is not None:
182
+ config_kwargs["credentials"] = credentials
183
+ if self._database_id is not None:
184
+ config_kwargs["database_id"] = self._database_id
185
+ config = glide_sync.GlideClientConfiguration(**config_kwargs)
186
+ client = glide_sync.GlideClient.create(config)
187
+
188
+ self._local.client = client
189
+
190
+ if self._has_buffer_get is None:
191
+ self._has_buffer_get = "buffer" in inspect.signature(client.get).parameters
192
+
193
+ return client
194
+
195
+ @property
196
+ def has_buffer_get(self) -> bool:
197
+ """Whether the GLIDE client supports buffer GET."""
198
+ if self._has_buffer_get is None:
199
+ self._executor.submit(self._get_client).result(
200
+ timeout=self._connection_timeout_ms / 1000
201
+ )
202
+ return bool(self._has_buffer_get)
203
+
204
+ def _do_set(self, key_str: str, data: bytes) -> None:
205
+ """SET a key (runs on a worker thread)."""
206
+ self._get_client().set(key_str.encode(), data)
207
+
208
+ def _do_get_into(self, key_str: str, buf: memoryview) -> bool:
209
+ """GET a key into a buffer (runs on a worker thread)."""
210
+ client = self._get_client()
211
+ if self._has_buffer_get:
212
+ result = client.get(key_str.encode(), buffer=buf)
213
+ return result is not None
214
+ else:
215
+ data = client.get(key_str.encode())
216
+ if data is None:
217
+ return False
218
+ buf[: len(data)] = data
219
+ return True
220
+
221
+ def _do_exists(self, key_str: str) -> bool:
222
+ """Check if a key exists (runs on a worker thread)."""
223
+ return bool(self._get_client().exists([key_str.encode()]))
224
+
225
+ def submit_set(self, key_str: str, data: bytes) -> Future:
226
+ """Submit a SET operation."""
227
+ return self._executor.submit(self._do_set, key_str, data)
228
+
229
+ def submit_get_into(self, key_str: str, buf: memoryview) -> Future:
230
+ """Submit a GET-into-buffer operation."""
231
+ return self._executor.submit(self._do_get_into, key_str, buf)
232
+
233
+ def submit_exists(self, key_str: str) -> Future:
234
+ """Submit an EXISTS check."""
235
+ return self._executor.submit(self._do_exists, key_str)
236
+
237
+ def _close_client(self) -> None:
238
+ """Close the per-thread GLIDE client (runs on a worker thread)."""
239
+ client = getattr(self._local, "client", None)
240
+ if client is not None:
241
+ try:
242
+ client.close()
243
+ except Exception as exc:
244
+ logger.debug("Error closing per-thread GLIDE client: %s", exc)
245
+ self._local.client = None
246
+
247
+ def close(self) -> None:
248
+ """Shut down all per-thread GLIDE clients and the thread pool."""
249
+ close_futs = [
250
+ self._executor.submit(self._close_client) for _ in range(self.num_workers)
251
+ ]
252
+ for f in close_futs:
253
+ try:
254
+ f.result(timeout=self._request_timeout)
255
+ except Exception as exc:
256
+ logger.debug("Error during client close: %s", exc)
257
+ self._executor.shutdown(wait=True, cancel_futures=False)
258
+ logger.info("Valkey thread pool closed")
259
+
260
+
261
+ class ValkeyConnector(RemoteConnector):
262
+ """High-throughput Valkey connector using GLIDE sync cluster client with
263
+ per-thread clients, ThreadPoolExecutor, and priority scheduling.
264
+
265
+ Uses N worker threads, each with its own GLIDE sync client, and
266
+ direct memoryview access for zero-copy data transfer. Single-key
267
+ storage halves Valkey round-trips compared to the legacy 2-key split.
268
+
269
+ Operations are dispatched through an ``AsyncPQExecutor`` with priority
270
+ levels (PEEK > PREFETCH > GET > PUT) so that latency-sensitive lookups
271
+ are not delayed behind bulk writes.
272
+
273
+ Args:
274
+ host: Valkey server hostname.
275
+ port: Valkey server port.
276
+ loop: Asyncio event loop (used for PQ executor and wrap_future).
277
+ local_cpu_backend: Backend for allocating CPU memory objects.
278
+ num_workers: Number of worker threads (default 8).
279
+ username: Valkey authentication username.
280
+ password: Valkey authentication password.
281
+ request_timeout: Timeout in seconds for requests and
282
+ Future.result() calls (default 5).
283
+ connection_timeout: Timeout in seconds for initial client
284
+ connections (default 10).
285
+ tls_enable: Whether to use TLS for Valkey connections.
286
+ cluster_mode: If True, use GlideClusterClient; else GlideClient.
287
+ database_id: Database ID for standalone mode (ignored in cluster).
288
+ """
289
+
290
+ def __init__(
291
+ self,
292
+ host: str,
293
+ port: int,
294
+ loop: asyncio.AbstractEventLoop,
295
+ local_cpu_backend: LocalCPUBackend,
296
+ num_workers: int = 8,
297
+ username: str = "",
298
+ password: str = "",
299
+ request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECS,
300
+ connection_timeout: float = DEFAULT_CONNECTION_TIMEOUT_SECS,
301
+ tls_enable: bool = False,
302
+ cluster_mode: bool = False,
303
+ database_id: Optional[int] = None,
304
+ ):
305
+ super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
306
+
307
+ self.host = host
308
+ self.port = port
309
+ self.num_workers = num_workers
310
+ self._request_timeout = request_timeout
311
+ self.loop = loop
312
+ self.local_cpu_backend = local_cpu_backend
313
+
314
+ self._pool = _ThreadWorkerPool(
315
+ host,
316
+ port,
317
+ num_workers,
318
+ username,
319
+ password,
320
+ request_timeout=request_timeout,
321
+ connection_timeout=connection_timeout,
322
+ tls_enable=tls_enable,
323
+ cluster_mode=cluster_mode,
324
+ database_id=database_id,
325
+ )
326
+ self._pq_executor = AsyncPQExecutor(loop)
327
+
328
+ # ── EXISTS ───────────────────────────────────────────────────────────
329
+
330
+ async def _exists(self, key: CacheEngineKey) -> bool:
331
+ """Internal: check if a key exists in Valkey."""
332
+ return await asyncio.wrap_future(self._pool.submit_exists(key.to_string()))
333
+
334
+ async def exists(self, key: CacheEngineKey) -> bool:
335
+ """Check if a key exists in Valkey."""
336
+ return await self._pq_executor.submit_job(
337
+ self._exists, key=key, priority=Priorities.PEEK
338
+ )
339
+
340
+ def exists_sync(self, key: CacheEngineKey) -> bool:
341
+ """Synchronously check if a key exists in Valkey."""
342
+ return self._pool.submit_exists(key.to_string()).result(
343
+ timeout=self._request_timeout
344
+ )
345
+
346
+ # ── GET ──────────────────────────────────────────────────────────────
347
+
348
+ async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
349
+ """Internal: retrieve a memory object from Valkey by key."""
350
+ memory_obj = self.local_cpu_backend.allocate(
351
+ self.meta_shapes, self.meta_dtypes, self.meta_fmt
352
+ )
353
+ if memory_obj is None:
354
+ logger.warning("Failed to allocate memory during remote receive")
355
+ return None
356
+
357
+ dst = memory_obj.byte_array
358
+ if not isinstance(dst, memoryview):
359
+ dst = memoryview(dst)
360
+ if dst.format != "B":
361
+ dst = dst.cast("B")
362
+
363
+ try:
364
+ found = await asyncio.wrap_future(
365
+ self._pool.submit_get_into(key.to_string(), dst)
366
+ )
367
+ except Exception:
368
+ memory_obj.ref_count_down()
369
+ raise
370
+
371
+ if not found:
372
+ memory_obj.ref_count_down()
373
+ return None
374
+ return memory_obj
375
+
376
+ async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
377
+ """Retrieve a memory object from Valkey by key."""
378
+ return await self._pq_executor.submit_job(
379
+ self._get, key=key, priority=Priorities.GET
380
+ )
381
+
382
+ # ── PUT ──────────────────────────────────────────────────────────────
383
+
384
+ async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj) -> None:
385
+ """Internal: store a memory object in Valkey.
386
+
387
+ Note: This method does NOT call ``ref_count_down()`` on the memory
388
+ object. Reference counting is managed by the caller
389
+ (``RemoteBackend.submit_put_task``), which increments before
390
+ submitting and decrements in the done callback.
391
+ """
392
+ await asyncio.wrap_future(
393
+ self._pool.submit_set(key.to_string(), memory_obj.byte_array)
394
+ )
395
+
396
+ async def put(self, key: CacheEngineKey, memory_obj: MemoryObj) -> None:
397
+ """Store a memory object in Valkey."""
398
+ await self._pq_executor.submit_job(
399
+ self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
400
+ )
401
+
402
+ # ── BATCHED PUT ──────────────────────────────────────────────────────
403
+
404
+ def support_batched_put(self) -> bool:
405
+ """Returns True — batched put is supported."""
406
+ return True
407
+
408
+ async def _batched_put(
409
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
410
+ ) -> None:
411
+ """Internal: store multiple memory objects in Valkey in parallel."""
412
+ n = len(keys)
413
+ key_strs = [k.to_string() for k in keys]
414
+
415
+ futures = [
416
+ self._pool.submit_set(key_strs[i], memory_objs[i].byte_array)
417
+ for i in range(n)
418
+ ]
419
+ wrapped = [asyncio.wrap_future(f) for f in futures]
420
+ await asyncio.gather(*wrapped)
421
+
422
+ async def batched_put(
423
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
424
+ ) -> None:
425
+ """Store multiple memory objects in Valkey in parallel."""
426
+ await self._pq_executor.submit_job(
427
+ self._batched_put,
428
+ keys=keys,
429
+ memory_objs=memory_objs,
430
+ priority=Priorities.PUT,
431
+ )
432
+
433
+ # ── BATCHED GET ──────────────────────────────────────────────────────
434
+
435
+ def support_batched_get(self) -> bool:
436
+ """Returns True — batched get is supported."""
437
+ return True
438
+
439
+ async def _batched_get(
440
+ self, keys: List[CacheEngineKey]
441
+ ) -> List[Optional[MemoryObj]]:
442
+ """Internal: retrieve multiple memory objects from Valkey in parallel.
443
+
444
+ Note: Once an allocation failure occurs, all subsequent slots are
445
+ set to ``None`` (intentional — memory pressure means further
446
+ allocations would also fail).
447
+ """
448
+ n = len(keys)
449
+ key_strs = [k.to_string() for k in keys]
450
+
451
+ memory_objs: List[Optional[MemoryObj]] = []
452
+ dst_bufs: List[Optional[memoryview]] = []
453
+ alloc_failed = False
454
+
455
+ for _ in keys:
456
+ if alloc_failed:
457
+ memory_objs.append(None)
458
+ dst_bufs.append(None)
459
+ continue
460
+
461
+ mobj = self.local_cpu_backend.allocate(
462
+ self.meta_shapes, self.meta_dtypes, self.meta_fmt
463
+ )
464
+ if mobj is None:
465
+ logger.warning(
466
+ "Failed to allocate memory during batched remote receive"
467
+ )
468
+ alloc_failed = True
469
+ memory_objs.append(None)
470
+ dst_bufs.append(None)
471
+ continue
472
+
473
+ memory_objs.append(mobj)
474
+ dst = mobj.byte_array
475
+ if not isinstance(dst, memoryview):
476
+ dst = memoryview(dst)
477
+ if dst.format != "B":
478
+ dst = dst.cast("B")
479
+ dst_bufs.append(dst)
480
+
481
+ # Submit GET futures for allocated slots; track which indices have
482
+ # real futures vs None (allocation failed).
483
+ live_indices: List[int] = []
484
+ live_futures: List[asyncio.Future] = []
485
+ for i in range(n):
486
+ if memory_objs[i] is not None and dst_bufs[i] is not None:
487
+ live_indices.append(i)
488
+ live_futures.append(
489
+ asyncio.wrap_future(
490
+ self._pool.submit_get_into(
491
+ key_strs[i],
492
+ dst_bufs[i], # type: ignore[arg-type]
493
+ )
494
+ )
495
+ )
496
+
497
+ try:
498
+ results = await asyncio.gather(*live_futures)
499
+ for idx, found in zip(live_indices, results, strict=True):
500
+ if not found:
501
+ memory_objs[idx].ref_count_down() # type: ignore[union-attr]
502
+ memory_objs[idx] = None
503
+ except Exception:
504
+ for mobj in memory_objs:
505
+ if mobj is not None:
506
+ mobj.ref_count_down()
507
+ raise
508
+
509
+ return memory_objs
510
+
511
+ async def batched_get(
512
+ self, keys: List[CacheEngineKey]
513
+ ) -> List[Optional[MemoryObj]]:
514
+ """Retrieve multiple memory objects from Valkey in parallel."""
515
+ return await self._pq_executor.submit_job(
516
+ self._batched_get, keys=keys, priority=Priorities.GET
517
+ )
518
+
519
+ # ── BATCHED CONTAINS ─────────────────────────────────────────────────
520
+
521
+ def support_batched_contains(self) -> bool:
522
+ """Returns True — synchronous batched contains is supported."""
523
+ return True
524
+
525
+ def _count_consecutive_exists(self, keys: List[CacheEngineKey]) -> int:
526
+ """Check how many consecutive keys exist (prefix match).
527
+
528
+ Fans out individual EXISTS checks across the thread pool for
529
+ parallel round-trips: wall-clock time is roughly
530
+ ``ceil(N / num_workers) * RTT`` instead of ``N * RTT``.
531
+ """
532
+ key_strs = [k.to_string() for k in keys]
533
+ futures = [self._pool.submit_exists(k) for k in key_strs]
534
+ for i, fut in enumerate(futures):
535
+ if not fut.result(timeout=self._request_timeout):
536
+ return i
537
+ return len(futures)
538
+
539
+ def batched_contains(self, keys: List[CacheEngineKey]) -> int:
540
+ """Synchronously check how many consecutive keys exist."""
541
+ return self._count_consecutive_exists(keys)
542
+
543
+ def support_batched_async_contains(self) -> bool:
544
+ """Returns True — async batched contains is supported."""
545
+ return True
546
+
547
+ async def _batched_async_contains(
548
+ self,
549
+ keys: List[CacheEngineKey],
550
+ ) -> int:
551
+ """Internal: asynchronously check how many consecutive keys exist.
552
+
553
+ Fans out individual EXISTS checks across the thread pool.
554
+ """
555
+ key_strs = [k.to_string() for k in keys]
556
+ wrapped = [asyncio.wrap_future(self._pool.submit_exists(k)) for k in key_strs]
557
+ results = await asyncio.gather(*wrapped)
558
+ for i, r in enumerate(results):
559
+ if not r:
560
+ return i
561
+ return len(results)
562
+
563
+ async def batched_async_contains(
564
+ self,
565
+ lookup_id: str,
566
+ keys: List[CacheEngineKey],
567
+ pin: bool = False,
568
+ ) -> int:
569
+ """Asynchronously check how many consecutive keys exist."""
570
+ return await self._pq_executor.submit_job(
571
+ self._batched_async_contains,
572
+ keys=keys,
573
+ priority=Priorities.PREFETCH,
574
+ )
575
+
576
+ def support_batched_get_non_blocking(self) -> bool:
577
+ """Returns True — non-blocking batched get is supported."""
578
+ return True
579
+
580
+ async def _batched_get_non_blocking(
581
+ self,
582
+ keys: List[CacheEngineKey],
583
+ ) -> List[MemoryObj]:
584
+ """Internal: non-blocking batched get returning the consecutive prefix.
585
+
586
+ Only the consecutive prefix of non-None memory objects (from the
587
+ beginning) is returned. Once a ``None`` (missing key or allocation
588
+ failure) is encountered, all subsequent objects — even if they were
589
+ successfully retrieved — are released and excluded. This matches
590
+ the base-class contract.
591
+ """
592
+ all_results = await self._batched_get(keys)
593
+
594
+ prefix: List[MemoryObj] = []
595
+ found_failure = False
596
+ for result in all_results:
597
+ if found_failure:
598
+ if result is not None:
599
+ result.ref_count_down()
600
+ elif result is not None:
601
+ prefix.append(result)
602
+ else:
603
+ found_failure = True
604
+
605
+ return prefix
606
+
607
+ async def batched_get_non_blocking(
608
+ self,
609
+ lookup_id: str,
610
+ keys: List[CacheEngineKey],
611
+ ) -> List[MemoryObj]:
612
+ """Non-blocking batched get returning the consecutive prefix."""
613
+ return await self._pq_executor.submit_job(
614
+ self._batched_get_non_blocking,
615
+ keys=keys,
616
+ priority=Priorities.PREFETCH,
617
+ )
618
+
619
+ async def list(self) -> List[str]:
620
+ """List all keys (not implemented)."""
621
+ return []
622
+
623
+ async def close(self) -> None:
624
+ """Shut down the PQ executor and the thread pool."""
625
+ await self._pq_executor.shutdown_async(wait=True)
626
+ self._pool.close()
627
+ logger.info("Closed ValkeyConnector")