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,828 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from enum import IntEnum, auto
4
+ from typing import List, Optional, Tuple, no_type_check
5
+ import asyncio
6
+ import inspect
7
+ import os
8
+
9
+ # Third Party
10
+ from redis.asyncio.cluster import ClusterNode, RedisCluster
11
+ import redis.asyncio as redis
12
+
13
+ # First Party
14
+ from lmcache.logging import init_logger
15
+ from lmcache.utils import CacheEngineKey
16
+ from lmcache.v1.memory_management import MemoryObj
17
+ from lmcache.v1.protocol import RemoteMetadata
18
+ from lmcache.v1.storage_backend.connector.base_connector import RemoteConnector
19
+ from lmcache.v1.storage_backend.job_executor.pq_executor import AsyncPQExecutor
20
+ from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
21
+ from lmcache.v1.storage_backend.native_clients.resp_client import RESPClient
22
+
23
+ logger = init_logger(__name__)
24
+
25
+ # TODO(Jiayi): Use `redis.asyncio`
26
+ # NOTE(Jiayi): `redis-py` supports async operations, but data copy
27
+ # cannot be avoided. `hiredis` is more lower-level but asyncio is
28
+ # not supported.
29
+
30
+
31
+ class Priorities(IntEnum):
32
+ PEEK = auto()
33
+ PREFETCH = auto()
34
+ GET = auto()
35
+ PUT = auto()
36
+
37
+
38
+ class RESPConnector(RemoteConnector):
39
+ """
40
+ The remote url should start with "resp://" and only have one host-port pair
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ host: str,
46
+ port: int,
47
+ loop: asyncio.AbstractEventLoop,
48
+ local_cpu_backend: LocalCPUBackend,
49
+ num_threads: int = 8,
50
+ username: str = "",
51
+ password: str = "",
52
+ ):
53
+ # this gives us access to self.full_chunk_size_bytes
54
+ super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
55
+
56
+ self.host = host
57
+ self.port = port
58
+ self.num_threads = num_threads
59
+ self.loop = loop
60
+ self.local_cpu_backend = local_cpu_backend
61
+
62
+ self.client = RESPClient(host, port, num_threads, loop, username, password)
63
+ self.pq_executor = AsyncPQExecutor(loop)
64
+
65
+ async def _exists(self, key: CacheEngineKey) -> bool:
66
+ return await self.client.exists(key.to_string())
67
+
68
+ async def exists(self, key: CacheEngineKey) -> bool:
69
+ return await self.pq_executor.submit_job(
70
+ self._exists, key=key, priority=Priorities.PEEK
71
+ )
72
+
73
+ def exists_sync(self, key: CacheEngineKey) -> bool:
74
+ return self.client.exists_sync(key.to_string())
75
+
76
+ async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
77
+ key_str = key.to_string()
78
+ memory_obj = self.local_cpu_backend.allocate(
79
+ self.meta_shapes,
80
+ self.meta_dtypes,
81
+ self.meta_fmt,
82
+ )
83
+
84
+ # byte array view of tensor
85
+ recv_buf = memory_obj.byte_array
86
+ if not isinstance(recv_buf, memoryview):
87
+ recv_buf = memoryview(recv_buf)
88
+ await self.client.get(key_str, recv_buf)
89
+ return memory_obj
90
+
91
+ async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
92
+ return await self.pq_executor.submit_job(
93
+ self._get, key=key, priority=Priorities.GET
94
+ )
95
+
96
+ async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
97
+ key_str = key.to_string()
98
+ send_buf = memory_obj.byte_array
99
+ if not isinstance(send_buf, memoryview):
100
+ send_buf = memoryview(send_buf)
101
+ await self.client.set(key_str, send_buf)
102
+
103
+ async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
104
+ await self.pq_executor.submit_job(
105
+ self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
106
+ )
107
+
108
+ def support_batched_put(self) -> bool:
109
+ return True
110
+
111
+ async def _batched_put(
112
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
113
+ ):
114
+ key_strs = [key.to_string() for key in keys]
115
+ send_bufs = [
116
+ memory_obj.byte_array
117
+ if isinstance(memory_obj.byte_array, memoryview)
118
+ else memoryview(memory_obj.byte_array)
119
+ for memory_obj in memory_objs
120
+ ]
121
+ await self.client.batch_set(key_strs, send_bufs)
122
+
123
+ async def batched_put(
124
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
125
+ ):
126
+ await self.pq_executor.submit_job(
127
+ self._batched_put,
128
+ keys=keys,
129
+ memory_objs=memory_objs,
130
+ priority=Priorities.PUT,
131
+ )
132
+
133
+ def support_batched_get(self) -> bool:
134
+ return True
135
+
136
+ async def _batched_get(
137
+ self, keys: List[CacheEngineKey]
138
+ ) -> List[Optional[MemoryObj]]:
139
+ key_strs = [key.to_string() for key in keys]
140
+ memory_objs = [
141
+ self.local_cpu_backend.allocate(
142
+ self.meta_shapes,
143
+ self.meta_dtypes,
144
+ self.meta_fmt,
145
+ )
146
+ for _ in keys
147
+ ]
148
+ recv_bufs = [
149
+ memory_obj.byte_array
150
+ if isinstance(memory_obj.byte_array, memoryview)
151
+ else memoryview(memory_obj.byte_array)
152
+ for memory_obj in memory_objs
153
+ ]
154
+ await self.client.batch_get(key_strs, recv_bufs)
155
+ return memory_objs
156
+
157
+ async def batched_get(
158
+ self, keys: List[CacheEngineKey]
159
+ ) -> List[Optional[MemoryObj]]:
160
+ return await self.pq_executor.submit_job(
161
+ self._batched_get, keys=keys, priority=Priorities.GET
162
+ )
163
+
164
+ def support_batched_contains(self) -> bool:
165
+ return True
166
+
167
+ def batched_contains(self, keys: List[CacheEngineKey]) -> int:
168
+ """Synchronous batched contains - checks consecutive prefix existence."""
169
+ key_strs = [key.to_string() for key in keys]
170
+ results = self.client.batch_exists_sync(key_strs)
171
+ count = 0
172
+ # we only want the prefixes
173
+ for result in results:
174
+ if not result:
175
+ return count
176
+ count += 1
177
+ return count
178
+
179
+ async def _batched_async_contains(self, keys: List[CacheEngineKey]) -> int:
180
+ key_strs = [key.to_string() for key in keys]
181
+ results = await self.client.batch_exists(key_strs)
182
+ count = 0
183
+ # we only want the prefixes
184
+ for result in results:
185
+ if not result:
186
+ return count
187
+ count += 1
188
+ return count
189
+
190
+ def support_batched_async_contains(self) -> bool:
191
+ return True
192
+
193
+ async def batched_async_contains(
194
+ self,
195
+ lookup_id: str,
196
+ keys: List[CacheEngineKey],
197
+ pin: bool = False,
198
+ ) -> int:
199
+ """Check how many keys exist in file system in batch
200
+
201
+ Args:
202
+ lookup_id: Identifier for this lookup operation
203
+ keys: List of keys to check
204
+ pin: Whether to pin the keys (not used in FS connector)
205
+
206
+ Returns:
207
+ Number of consecutive keys that exist, starting from the first key
208
+ """
209
+ # prefetch priority
210
+ return await self.pq_executor.submit_job(
211
+ self._batched_async_contains, keys=keys, priority=Priorities.PREFETCH
212
+ )
213
+
214
+ def support_batched_get_non_blocking(self) -> bool:
215
+ return True
216
+
217
+ async def batched_get_non_blocking(
218
+ self,
219
+ lookup_id: str,
220
+ keys: List[CacheEngineKey],
221
+ ) -> List[MemoryObj]:
222
+ # prefetch priority
223
+ return await self.pq_executor.submit_job(
224
+ self._batched_get, keys=keys, priority=Priorities.PREFETCH
225
+ )
226
+
227
+ # TODO
228
+ @no_type_check
229
+ async def list(self) -> List[str]:
230
+ pass
231
+
232
+ async def close(self):
233
+ await self.pq_executor.shutdown(wait=True)
234
+ self.client.close()
235
+ logger.info("Closed the RESP connection")
236
+
237
+
238
+ class RedisConnector(RemoteConnector):
239
+ """
240
+ The remote url should start with "redis://" and only have one host-port pair
241
+ """
242
+
243
+ def __init__(
244
+ self,
245
+ url: str,
246
+ loop: asyncio.AbstractEventLoop,
247
+ local_cpu_backend: LocalCPUBackend,
248
+ ):
249
+ # initialize base class, which includes some common attributes
250
+ super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
251
+
252
+ # set a large max
253
+ self.max_connections = 150
254
+ # redis will crash if we have more than max_connections connections
255
+ self.sem = asyncio.Semaphore(self.max_connections)
256
+ self.pool = redis.ConnectionPool.from_url(
257
+ url, max_connections=self.max_connections
258
+ )
259
+ self.connection = redis.Redis.from_pool(self.pool)
260
+ self.loop = loop
261
+ self.local_cpu_backend = local_cpu_backend
262
+
263
+ self.pq_executor = AsyncPQExecutor(loop)
264
+
265
+ async def _exists(self, key: CacheEngineKey) -> bool:
266
+ async with self.sem:
267
+ return bool(await self.connection.exists(key.to_string() + "metadata"))
268
+
269
+ async def exists(self, key: CacheEngineKey) -> bool:
270
+ return await self.pq_executor.submit_job(
271
+ self._exists, key=key, priority=Priorities.PEEK
272
+ )
273
+
274
+ def exists_sync(self, key: CacheEngineKey) -> bool:
275
+ future = asyncio.run_coroutine_threadsafe(self.exists(key), self.loop)
276
+ return bool(future.result())
277
+
278
+ async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
279
+ key_str = key.to_string()
280
+ async with self.sem:
281
+ metadata_bytes = await self.connection.get(key_str + "metadata")
282
+
283
+ if metadata_bytes is None:
284
+ return None
285
+
286
+ assert not inspect.isawaitable(metadata_bytes)
287
+
288
+ metadata = RemoteMetadata.deserialize(memoryview(metadata_bytes))
289
+
290
+ memory_obj = self.local_cpu_backend.allocate(
291
+ metadata.shapes,
292
+ metadata.dtypes,
293
+ metadata.fmt,
294
+ )
295
+ if memory_obj is None:
296
+ logger.warning("Failed to allocate memory during remote receive")
297
+ return None
298
+
299
+ # TODO(Jiayi): Find a way to do `get` inplace
300
+ kv_bytes = await self.connection.get(key_str + "kv_bytes")
301
+ assert not inspect.isawaitable(kv_bytes)
302
+
303
+ if kv_bytes is None:
304
+ # TODO (Jiayi): We might need a way to better handle
305
+ # consistency issues.
306
+ # TODO (Jiayi): A better way is to aggregate metadata
307
+ # and kv cache in one key.
308
+ logger.warning(
309
+ "Key exists but KV cache does not exist."
310
+ "Might happen when the cache is evicted by redis."
311
+ )
312
+ async with self.sem:
313
+ await self.connection.delete(key_str + "metadata")
314
+ return None
315
+
316
+ if isinstance(memory_obj.byte_array, memoryview):
317
+ view = memory_obj.byte_array
318
+ if view.format == "<B":
319
+ view = view.cast("B")
320
+ else:
321
+ view = memoryview(memory_obj.byte_array)
322
+
323
+ if isinstance(kv_bytes, (bytes, bytearray)):
324
+ view[: metadata.length] = kv_bytes
325
+ elif isinstance(kv_bytes, str):
326
+ converted = kv_bytes.encode("utf-8")
327
+ view[: metadata.length] = converted
328
+ else:
329
+ converted = bytes(kv_bytes)
330
+ view[: metadata.length] = converted
331
+
332
+ return memory_obj
333
+
334
+ async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
335
+ return await self.pq_executor.submit_job(
336
+ self._get, key=key, priority=Priorities.GET
337
+ )
338
+
339
+ def support_batched_put(self) -> bool:
340
+ return True
341
+
342
+ async def _batched_put(
343
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
344
+ ):
345
+ # calling self.put will create a circular dependency
346
+ await asyncio.gather(
347
+ *(
348
+ self._put(key, memory_obj)
349
+ for key, memory_obj in zip(keys, memory_objs, strict=False)
350
+ )
351
+ )
352
+
353
+ async def batched_put(
354
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
355
+ ):
356
+ await self.pq_executor.submit_job(
357
+ self._batched_put,
358
+ keys=keys,
359
+ memory_objs=memory_objs,
360
+ priority=Priorities.PUT,
361
+ )
362
+
363
+ async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
364
+ # TODO(Jiayi): The following code is ugly.
365
+ # Please use a function like `memory_obj.to_meta()`.
366
+ kv_bytes = memory_obj.byte_array
367
+ kv_shapes = memory_obj.get_shapes()
368
+ kv_dtypes = memory_obj.get_dtypes()
369
+ memory_format = memory_obj.get_memory_format()
370
+
371
+ metadata_bytes = RemoteMetadata(
372
+ len(kv_bytes), kv_shapes, kv_dtypes, memory_format
373
+ ).serialize()
374
+
375
+ key_str = key.to_string()
376
+ # kv bytes needs to be set first to avoid race condition
377
+ async with self.sem:
378
+ await self.connection.set(key_str + "kv_bytes", kv_bytes)
379
+ await self.connection.set(key_str + "metadata", metadata_bytes)
380
+
381
+ async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
382
+ await self.pq_executor.submit_job(
383
+ self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
384
+ )
385
+
386
+ # TODO
387
+ @no_type_check
388
+ async def list(self) -> List[str]:
389
+ pass
390
+
391
+ async def close(self):
392
+ await self.pq_executor.shutdown(wait=True)
393
+ await self.connection.close()
394
+ logger.info("Closed the redis connection")
395
+
396
+ def support_batched_async_contains(self) -> bool:
397
+ return True
398
+
399
+ async def _batched_async_contains(
400
+ self,
401
+ lookup_id: str,
402
+ keys: List[CacheEngineKey],
403
+ pin: bool = False,
404
+ ) -> int:
405
+ num_hit_counts = 0
406
+ for key in keys:
407
+ async with self.sem:
408
+ if not await self.connection.exists(key.to_string() + "metadata"):
409
+ return num_hit_counts
410
+ num_hit_counts += 1
411
+ return num_hit_counts
412
+
413
+ async def batched_async_contains(
414
+ self,
415
+ lookup_id: str,
416
+ keys: List[CacheEngineKey],
417
+ pin: bool = False,
418
+ ) -> int:
419
+ return await self.pq_executor.submit_job(
420
+ self._batched_async_contains,
421
+ lookup_id=lookup_id,
422
+ keys=keys,
423
+ pin=pin,
424
+ priority=Priorities.PEEK,
425
+ )
426
+
427
+ def support_batched_get_non_blocking(self) -> bool:
428
+ return True
429
+
430
+ async def _batched_get_non_blocking(
431
+ self,
432
+ lookup_id: str,
433
+ keys: List[CacheEngineKey],
434
+ ) -> List[MemoryObj]:
435
+ # calling self.get will create a circular dependency
436
+ results = await asyncio.gather(*(self._get(key) for key in keys))
437
+ return [r for r in results if r is not None]
438
+
439
+ async def batched_get_non_blocking(
440
+ self,
441
+ lookup_id: str,
442
+ keys: List[CacheEngineKey],
443
+ ) -> List[MemoryObj]:
444
+ return await self.pq_executor.submit_job(
445
+ self._batched_get_non_blocking,
446
+ lookup_id=lookup_id,
447
+ keys=keys,
448
+ priority=Priorities.PREFETCH,
449
+ )
450
+
451
+
452
+ class RedisSentinelConnector(RemoteConnector):
453
+ """
454
+ Uses redis.Sentinel to connect to a Redis cluster.
455
+ The hosts are specified in the config file, started with "redis-sentinel://"
456
+ and separated by commas.
457
+
458
+ Example:
459
+ remote_url: "redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
460
+
461
+ Extra environment variables:
462
+ - REDIS_SERVICE_NAME (required) -- service name for redis.
463
+ - REDIS_TIMEOUT (optional) -- Timeout in seconds, default is 1 if not set
464
+ """
465
+
466
+ ENV_REDIS_TIMEOUT = "REDIS_TIMEOUT"
467
+ ENV_REDIS_SERVICE_NAME = "REDIS_SERVICE_NAME"
468
+
469
+ def __init__(
470
+ self,
471
+ hosts_and_ports: List[Tuple[str, int]],
472
+ username: str,
473
+ password: str,
474
+ loop: asyncio.AbstractEventLoop,
475
+ local_cpu_backend: LocalCPUBackend,
476
+ ):
477
+ # initialize base class, which includes some common attributes
478
+ super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
479
+
480
+ # Get service name
481
+ match os.environ.get(self.ENV_REDIS_SERVICE_NAME):
482
+ case None:
483
+ logger.warning(
484
+ f"Environment variable {self.ENV_REDIS_SERVICE_NAME} is "
485
+ f"not found, using default value 'redismaster'"
486
+ )
487
+ service_name = "redismaster"
488
+ case value:
489
+ service_name = value
490
+
491
+ timeout: float = -1000.0
492
+
493
+ # Get timeout
494
+ match os.environ.get(self.ENV_REDIS_TIMEOUT):
495
+ case None:
496
+ timeout = 1
497
+ case value:
498
+ timeout = float(value)
499
+
500
+ logger.info(f"Host and ports: {hosts_and_ports}")
501
+ self.sentinel = redis.Sentinel(hosts_and_ports, socket_timeout=timeout)
502
+ self.master = self.sentinel.master_for(
503
+ service_name, socket_timeout=timeout, username=username, password=password
504
+ )
505
+ self.slave = self.sentinel.slave_for(
506
+ service_name, socket_timeout=timeout, username=username, password=password
507
+ )
508
+
509
+ self.local_cpu_backend = local_cpu_backend
510
+
511
+ async def exists(self, key: CacheEngineKey) -> bool:
512
+ return bool(self.slave.exists(key.to_string() + "metadata"))
513
+
514
+ def exists_sync(self, key: CacheEngineKey) -> bool:
515
+ return bool(self.slave.exists(key.to_string() + "metadata"))
516
+
517
+ async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
518
+ key_str = key.to_string()
519
+ metadata_bytes = self.slave.get(key_str + "metadata")
520
+
521
+ if metadata_bytes is None:
522
+ return None
523
+
524
+ assert not inspect.isawaitable(metadata_bytes)
525
+
526
+ metadata = RemoteMetadata.deserialize(metadata_bytes)
527
+
528
+ memory_obj = self.local_cpu_backend.allocate(
529
+ metadata.shapes,
530
+ metadata.dtypes,
531
+ metadata.fmt,
532
+ )
533
+ if memory_obj is None:
534
+ logger.warning("Failed to allocate memory during remote receive")
535
+ return None
536
+
537
+ # TODO(Jiayi): Find a way to do `get` inplace
538
+ kv_bytes = self.slave.get(key_str + "kv_bytes")
539
+
540
+ assert not inspect.isawaitable(kv_bytes)
541
+
542
+ if kv_bytes is None:
543
+ # TODO (Jiayi): We might need a way to better handle
544
+ # consistency issues.
545
+ # TODO (Jiayi): A background sweeper might be better
546
+ # for the sake of performance.
547
+ logger.warning(
548
+ "Key exists but KV cache does not exist."
549
+ "Might happen when the cache is evicted by redis."
550
+ )
551
+ self.master.delete(key_str + "metadata")
552
+ return None
553
+
554
+ if isinstance(memory_obj.byte_array, memoryview):
555
+ view = memory_obj.byte_array
556
+ if view.format == "<B":
557
+ view = view.cast("B")
558
+ else:
559
+ view = memoryview(memory_obj.byte_array)
560
+
561
+ if isinstance(kv_bytes, (bytes, bytearray)):
562
+ view[0 : metadata.length] = kv_bytes
563
+ elif isinstance(kv_bytes, str):
564
+ converted = kv_bytes.encode("utf-8")
565
+ view[0 : metadata.length] = converted
566
+ else:
567
+ converted = bytes(kv_bytes)
568
+ view[0 : metadata.length] = converted
569
+
570
+ return memory_obj
571
+
572
+ async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
573
+ # TODO(Jiayi): The following code is ugly.
574
+ # Please use a function like `memory_obj.to_meta()`.
575
+ kv_bytes = memory_obj.byte_array
576
+ kv_shapes = memory_obj.get_shapes()
577
+ kv_dtypes = memory_obj.get_dtypes()
578
+ memory_format = memory_obj.get_memory_format()
579
+
580
+ metadata_bytes = RemoteMetadata(
581
+ len(kv_bytes), kv_shapes, kv_dtypes, memory_format
582
+ ).serialize()
583
+
584
+ key_str = key.to_string()
585
+ # kv bytes needs to be set first to avoid race condition
586
+ self.master.set(key_str + "kv_bytes", kv_bytes)
587
+ self.master.set(key_str + "metadata", metadata_bytes)
588
+
589
+ # TODO
590
+ @no_type_check
591
+ async def list(self) -> List[str]:
592
+ pass
593
+
594
+ async def close(self):
595
+ self.master.close()
596
+ self.slave.close()
597
+
598
+
599
+ class RedisClusterConnector(RemoteConnector):
600
+ """
601
+ The remote url starts with "redis-cluster:// and can include one or
602
+ multiple hosts:ports, separated by commas.
603
+
604
+ Example:
605
+ remote_url: "redis-cluster://host1:7000,host2:7000,host3:7000"
606
+
607
+ Extra environment variables:
608
+ - REDIS_TIMEOUT (optional) -- Timeout in seconds, default is 1 if not set
609
+ """
610
+
611
+ def __init__(
612
+ self,
613
+ hosts_and_ports: List[Tuple[str, int]],
614
+ username: str,
615
+ password: str,
616
+ loop: asyncio.AbstractEventLoop,
617
+ local_cpu_backend: LocalCPUBackend,
618
+ ):
619
+ # initialize base class, which includes some common attributes
620
+ super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
621
+
622
+ # Convert hosts_and_ports to startup_nodes format expected by RedisCluster
623
+ startup_nodes = [ClusterNode(h, p) for (h, p) in hosts_and_ports]
624
+
625
+ # set a large max
626
+ self.max_connections = 150
627
+ # redis will crash if we have more than max_connections connections
628
+ self.sem = asyncio.Semaphore(self.max_connections)
629
+
630
+ # Initialize cluster connection
631
+ self.cluster = RedisCluster(
632
+ startup_nodes=startup_nodes,
633
+ username=username,
634
+ password=password,
635
+ max_connections=self.max_connections,
636
+ decode_responses=False,
637
+ )
638
+ self.loop = loop
639
+ self.local_cpu_backend = local_cpu_backend
640
+
641
+ self.pq_executor = AsyncPQExecutor(loop)
642
+
643
+ async def _exists(self, key: CacheEngineKey) -> bool:
644
+ async with self.sem:
645
+ return bool(await self.cluster.exists(key.to_string() + "metadata"))
646
+
647
+ async def exists(self, key: CacheEngineKey) -> bool:
648
+ return await self.pq_executor.submit_job(
649
+ self._exists, key=key, priority=Priorities.PEEK
650
+ )
651
+
652
+ def exists_sync(self, key: CacheEngineKey) -> bool:
653
+ future = asyncio.run_coroutine_threadsafe(self.exists(key), self.loop)
654
+ return bool(future.result())
655
+
656
+ async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
657
+ key_str = key.to_string()
658
+ async with self.sem:
659
+ metadata_bytes = await self.cluster.get(key_str + "metadata")
660
+
661
+ if metadata_bytes is None:
662
+ return None
663
+
664
+ assert not inspect.isawaitable(metadata_bytes)
665
+
666
+ metadata = RemoteMetadata.deserialize(memoryview(metadata_bytes))
667
+
668
+ memory_obj = self.local_cpu_backend.allocate(
669
+ metadata.shapes,
670
+ metadata.dtypes,
671
+ metadata.fmt,
672
+ )
673
+ if memory_obj is None:
674
+ logger.warning("Failed to allocate memory during remote receive")
675
+ return None
676
+
677
+ # TODO(Jiayi): Find a way to do `get` inplace
678
+ kv_bytes = await self.cluster.get(key_str + "kv_bytes")
679
+
680
+ assert not inspect.isawaitable(kv_bytes)
681
+
682
+ if kv_bytes is None:
683
+ # TODO (Jiayi): We might need a way to better handle
684
+ # consistency issues.
685
+ # TODO (Jiayi): A better way is to aggregate metadata
686
+ # and kv cache in one key.
687
+ logger.warning(
688
+ "Key exists but KV cache does not exist."
689
+ "Might happen when the cache is evicted by redis."
690
+ )
691
+ async with self.sem:
692
+ await self.cluster.delete(key_str + "metadata")
693
+ return None
694
+
695
+ if isinstance(memory_obj.byte_array, memoryview):
696
+ view = memory_obj.byte_array
697
+ if view.format == "<B":
698
+ view = view.cast("B")
699
+ else:
700
+ view = memoryview(memory_obj.byte_array)
701
+
702
+ if isinstance(kv_bytes, (bytes, bytearray)):
703
+ view[: metadata.length] = kv_bytes
704
+ elif isinstance(kv_bytes, str):
705
+ converted = kv_bytes.encode("utf-8")
706
+ view[: metadata.length] = converted
707
+ else:
708
+ converted = bytes(kv_bytes)
709
+ view[: metadata.length] = converted
710
+
711
+ return memory_obj
712
+
713
+ async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
714
+ return await self.pq_executor.submit_job(
715
+ self._get, key=key, priority=Priorities.GET
716
+ )
717
+
718
+ def support_batched_put(self) -> bool:
719
+ return True
720
+
721
+ async def _batched_put(
722
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
723
+ ):
724
+ # calling self.put will create a circular dependency
725
+ await asyncio.gather(
726
+ *(
727
+ self._put(key, memory_obj)
728
+ for key, memory_obj in zip(keys, memory_objs, strict=False)
729
+ )
730
+ )
731
+
732
+ async def batched_put(
733
+ self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
734
+ ):
735
+ await self.pq_executor.submit_job(
736
+ self._batched_put,
737
+ keys=keys,
738
+ memory_objs=memory_objs,
739
+ priority=Priorities.PUT,
740
+ )
741
+
742
+ async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
743
+ # TODO(Jiayi): The following code is ugly.
744
+ # Please use a function like `memory_obj.to_meta()`.
745
+ kv_bytes = memory_obj.byte_array
746
+ kv_shapes = memory_obj.get_shapes()
747
+ kv_dtypes = memory_obj.get_dtypes()
748
+ memory_format = memory_obj.get_memory_format()
749
+
750
+ metadata_bytes = RemoteMetadata(
751
+ len(kv_bytes), kv_shapes, kv_dtypes, memory_format
752
+ ).serialize()
753
+
754
+ key_str = key.to_string()
755
+ # kv bytes needs to be set first to avoid race condition
756
+ async with self.sem:
757
+ await self.cluster.set(key_str + "kv_bytes", kv_bytes)
758
+ await self.cluster.set(key_str + "metadata", metadata_bytes)
759
+
760
+ async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
761
+ await self.pq_executor.submit_job(
762
+ self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
763
+ )
764
+
765
+ # TODO
766
+ @no_type_check
767
+ async def list(self) -> List[str]:
768
+ pass
769
+
770
+ async def close(self):
771
+ await self.pq_executor.shutdown(wait=True)
772
+ await self.cluster.close()
773
+ logger.info("Closed the redis cluster connection")
774
+
775
+ def support_batched_async_contains(self) -> bool:
776
+ return True
777
+
778
+ async def _batched_async_contains(
779
+ self,
780
+ lookup_id: str,
781
+ keys: List[CacheEngineKey],
782
+ pin: bool = False,
783
+ ) -> int:
784
+ num_hit_counts = 0
785
+ for key in keys:
786
+ async with self.sem:
787
+ if not await self.cluster.exists(key.to_string() + "metadata"):
788
+ return num_hit_counts
789
+ num_hit_counts += 1
790
+ return num_hit_counts
791
+
792
+ async def batched_async_contains(
793
+ self,
794
+ lookup_id: str,
795
+ keys: List[CacheEngineKey],
796
+ pin: bool = False,
797
+ ) -> int:
798
+ return await self.pq_executor.submit_job(
799
+ self._batched_async_contains,
800
+ lookup_id=lookup_id,
801
+ keys=keys,
802
+ pin=pin,
803
+ priority=Priorities.PEEK,
804
+ )
805
+
806
+ def support_batched_get_non_blocking(self) -> bool:
807
+ return True
808
+
809
+ async def _batched_get_non_blocking(
810
+ self,
811
+ lookup_id: str,
812
+ keys: List[CacheEngineKey],
813
+ ) -> List[MemoryObj]:
814
+ # calling self.get will create a circular dependency
815
+ results = await asyncio.gather(*(self._get(key) for key in keys))
816
+ return [r for r in results if r is not None]
817
+
818
+ async def batched_get_non_blocking(
819
+ self,
820
+ lookup_id: str,
821
+ keys: List[CacheEngineKey],
822
+ ) -> List[MemoryObj]:
823
+ return await self.pq_executor.submit_job(
824
+ self._batched_get_non_blocking,
825
+ lookup_id=lookup_id,
826
+ keys=keys,
827
+ priority=Priorities.PREFETCH,
828
+ )