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,830 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Prefetch Controller: asynchronously prefetches data from L2 adapters into L1.
4
+
5
+ The controller runs a background thread with an event-driven loop that:
6
+ 1. Accepts prefetch requests from external threads via submit_prefetch_request.
7
+ 2. Submits lookup_and_lock tasks to all L2 adapters.
8
+ 3. Computes a load plan using the PrefetchPolicy, trimmed to the contiguous
9
+ prefix of found keys.
10
+ 4. Reserves L1 write buffers and submits load tasks to L2 adapters.
11
+ 5. On load completion, transitions L1 entries from write-locked to read-locked.
12
+ 6. Reports prefix hit count.
13
+ """
14
+
15
+ # Standard
16
+ from dataclasses import dataclass, field
17
+ from typing import Iterable
18
+ import enum
19
+ import os
20
+ import select
21
+ import threading
22
+
23
+ # First Party
24
+ from lmcache.logging import init_logger
25
+ from lmcache.native_storage_ops import Bitmap
26
+ from lmcache.v1.distributed.api import MemoryLayoutDesc, ObjectKey
27
+ from lmcache.v1.distributed.error import L1Error
28
+ from lmcache.v1.distributed.l1_manager import L1Manager
29
+ from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
30
+ from lmcache.v1.distributed.storage_controller import StorageControllerInterface
31
+ from lmcache.v1.distributed.storage_controllers.prefetch_policy import (
32
+ PrefetchPolicy,
33
+ )
34
+ from lmcache.v1.distributed.storage_controllers.store_policy import (
35
+ AdapterDescriptor,
36
+ )
37
+ from lmcache.v1.memory_management import MemoryObj
38
+ from lmcache.v1.mp_observability.event import Event, EventType
39
+ from lmcache.v1.mp_observability.event_bus import get_event_bus
40
+
41
+ logger = init_logger(__name__)
42
+
43
+
44
+ # HELPER FUNCTIONS
45
+ def trim_load_plan_to_first_n_keys(
46
+ load_plan: dict[int, Bitmap],
47
+ num_keys: int,
48
+ n: int,
49
+ ) -> dict[int, Bitmap]:
50
+ """
51
+ Trim the load plan to only include keys with indices < n.
52
+
53
+ For example, if n=3 and the combined load plan has keys
54
+ {0, 1, 3}, the trimmed plan will only include key indices [0, 1]
55
+ and exclude index 3.
56
+
57
+ Args:
58
+ load_plan: Mapping from adapter index to Bitmap of key indices.
59
+ num_keys: Total number of keys (bitmap size).
60
+ n: Number of keys to include in the trimmed plan (prefix length).
61
+
62
+ Returns:
63
+ Trimmed load plan with only key indices < n.
64
+
65
+ Note:
66
+ the adapter index will not appear in the return dict if
67
+ it has no keys in the prefix.
68
+ """
69
+ if n <= 0:
70
+ return {}
71
+
72
+ trimmed_plan: dict[int, Bitmap] = {}
73
+ mask_bitmap = Bitmap(num_keys, n)
74
+ for adapter_idx, bitmap in load_plan.items():
75
+ new_bitmap = bitmap & mask_bitmap
76
+ if new_bitmap.popcount() == 0:
77
+ continue
78
+
79
+ trimmed_plan[adapter_idx] = new_bitmap
80
+
81
+ return trimmed_plan
82
+
83
+
84
+ def trim_load_plan_to_prefix(
85
+ load_plan: dict[int, Bitmap],
86
+ num_keys: int,
87
+ ) -> dict[int, Bitmap]:
88
+ """
89
+ Trim the load plan to the longest contiguous prefix of keys.
90
+
91
+ For example, if num_keys=5 and the combined load plan has keys
92
+ {0, 1, 3}, the prefix is 2 (keys 0 and 1), so the trimmed plan
93
+ will only include key indices [0, 1] and exclude index 3.
94
+
95
+ Args:
96
+ load_plan: Mapping from adapter index to Bitmap of key indices.
97
+ num_keys: Total number of keys in the request.
98
+
99
+ Returns:
100
+ Trimmed load plan with only prefix key indices.
101
+
102
+ Note:
103
+ the adapter index will not appear in the return dict if
104
+ it has no keys in the prefix.
105
+ """
106
+ merged_plan = Bitmap(num_keys)
107
+ for bitmap in load_plan.values():
108
+ merged_plan = merged_plan | bitmap
109
+
110
+ prefix_length = merged_plan.count_leading_ones()
111
+ return trim_load_plan_to_first_n_keys(load_plan, num_keys, prefix_length)
112
+
113
+
114
+ def merge_bitmaps(bitmaps: Iterable[Bitmap], num_keys: int) -> Bitmap:
115
+ """Merge multiple bitmaps with a bitwise OR."""
116
+ if not bitmaps:
117
+ return Bitmap(0)
118
+ merged = Bitmap(num_keys)
119
+ for bm in bitmaps:
120
+ merged = merged | bm
121
+ return merged
122
+
123
+
124
+ # Poll timeout in milliseconds for the prefetch loop
125
+ PREFETCH_LOOP_POLL_TIMEOUT_MS = 500
126
+
127
+ PrefetchRequestId = int
128
+
129
+
130
+ class PrefetchPhase(enum.Enum):
131
+ LOOKUP = enum.auto()
132
+ PLAN_AND_LOAD = enum.auto()
133
+
134
+
135
+ @dataclass
136
+ class InFlightPrefetchRequest:
137
+ """Tracks a single prefetch request across its lifecycle phases."""
138
+
139
+ request_id: PrefetchRequestId
140
+ keys: list[ObjectKey]
141
+ layout_desc: MemoryLayoutDesc
142
+ phase: PrefetchPhase
143
+ extra_count: int = 0
144
+ """Extra read locks per key (on top of the default 1) to acquire when
145
+ transitioning from write-locked to read-locked. Must match the
146
+ ``extra_count`` used in the corresponding ``submit_prefetch_task`` call."""
147
+
148
+ # Lookup phase: adapter_idx -> task_id (removed as results arrive)
149
+ pending_lookup_tasks: dict[int, L2TaskId] = field(default_factory=dict)
150
+ # Lookup phase: adapter_idx -> bitmap (populated as results arrive)
151
+ lookup_results: dict[int, Bitmap] = field(default_factory=dict)
152
+
153
+ # Load phase: adapter_idx -> bitmap of key indices to load
154
+ load_plan: dict[int, Bitmap] = field(default_factory=dict)
155
+ # Load phase: adapter_idx -> task_id (removed as results arrive)
156
+ pending_load_tasks: dict[int, L2TaskId] = field(default_factory=dict)
157
+ # Load phase: adapter_idx -> bitmap (populated as results arrive)
158
+ load_results: dict[int, Bitmap] = field(default_factory=dict)
159
+ # Load phase: keys that were write-reserved in L1
160
+ write_reserved_keys: list[ObjectKey] = field(default_factory=list)
161
+ write_reserved_objs: dict[ObjectKey, MemoryObj] = field(default_factory=dict)
162
+
163
+ def all_lookups_done(self) -> bool:
164
+ return len(self.pending_lookup_tasks) == 0
165
+
166
+ def all_loads_done(self) -> bool:
167
+ return len(self.pending_load_tasks) == 0
168
+
169
+
170
+ class PrefetchController(StorageControllerInterface):
171
+ """
172
+ Asynchronously prefetches data from L2 adapters into L1 memory.
173
+
174
+ The controller:
175
+ 1. Accepts prefetch requests via submit_prefetch_request (thread-safe).
176
+ 2. Runs a background thread that submits lookup_and_lock to all adapters.
177
+ 3. Uses PrefetchPolicy to compute a load plan from lookup results.
178
+ 4. Reserves L1 write buffers and submits load tasks to adapters.
179
+ 5. On completion, transitions loaded keys to read-locked state.
180
+ 6. Reports the number of prefix hits via query_prefetch_result.
181
+
182
+ Args:
183
+ l1_manager: The L1 manager instance.
184
+ l2_adapters: List of L2 adapter instances.
185
+ adapter_descriptors: Descriptors for each L2 adapter (same order).
186
+ policy: The prefetch policy for load plan decisions.
187
+ max_in_flight: Maximum number of concurrent prefetch requests.
188
+ """
189
+
190
+ def __init__(
191
+ self,
192
+ l1_manager: L1Manager,
193
+ l2_adapters: list[L2AdapterInterface],
194
+ adapter_descriptors: list[AdapterDescriptor],
195
+ policy: PrefetchPolicy,
196
+ max_in_flight: int = 8,
197
+ ) -> None:
198
+ self._l1_manager = l1_manager
199
+ self._l2_adapters = l2_adapters
200
+ self._adapter_descriptors = adapter_descriptors
201
+ self._policy = policy
202
+ self._max_in_flight = max_in_flight
203
+
204
+ # In-flight request tracking (background thread only)
205
+ self._in_flight_requests: dict[PrefetchRequestId, InFlightPrefetchRequest] = {}
206
+ self._pending_queue: list[
207
+ tuple[PrefetchRequestId, list[ObjectKey], MemoryLayoutDesc, int]
208
+ ] = []
209
+
210
+ # Shadow counters for status reporting (updated in background loop)
211
+ self._status_in_flight_count: int = 0
212
+ self._status_pending_count: int = 0
213
+ self._status_lookup_phase_count: int = 0
214
+ self._status_load_phase_count: int = 0
215
+
216
+ # Thread-safe submission queue (external -> background)
217
+ self._submission_lock = threading.Lock()
218
+ self._submission_queue: list[
219
+ tuple[PrefetchRequestId, list[ObjectKey], MemoryLayoutDesc, int]
220
+ ] = []
221
+ self._next_request_id: PrefetchRequestId = 0
222
+ self._submission_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
223
+
224
+ # Thread-safe lookup results (background -> external)
225
+ self._lookup_results_lock = threading.Lock()
226
+ self._completed_lookups: dict[PrefetchRequestId, int] = {}
227
+
228
+ # Thread-safe prefetch results (background -> external)
229
+ self._prefetch_results_lock = threading.Lock()
230
+ self._completed_results: dict[PrefetchRequestId, int] = {}
231
+
232
+ # Map eventfds to adapter indices for quick lookup in poll.
233
+ # Relies on the L2AdapterInterface contract that every adapter
234
+ # returns distinct fds for store/lookup/load, and no two adapters
235
+ # share an fd. See the docstrings in L2AdapterInterface.
236
+ self._lookup_efd_to_adapter: dict[int, int] = {}
237
+ self._load_efd_to_adapter: dict[int, int] = {}
238
+ for i, adapter in enumerate(self._l2_adapters):
239
+ self._lookup_efd_to_adapter[adapter.get_lookup_and_lock_event_fd()] = i
240
+ self._load_efd_to_adapter[adapter.get_load_event_fd()] = i
241
+
242
+ self._event_bus = get_event_bus()
243
+
244
+ self._stop_flag = threading.Event()
245
+ self._thread = threading.Thread(
246
+ target=self._prefetch_loop,
247
+ daemon=True,
248
+ )
249
+
250
+ # =========================================================================
251
+ # External API (thread-safe)
252
+ # =========================================================================
253
+
254
+ def submit_prefetch_request(
255
+ self,
256
+ keys: list[ObjectKey],
257
+ layout_desc: MemoryLayoutDesc,
258
+ extra_count: int = 0,
259
+ ) -> PrefetchRequestId:
260
+ """
261
+ Submit a prefetch request for the given keys.
262
+
263
+ Thread-safe. Can be called from any thread.
264
+
265
+ Only the **contiguous prefix** of found keys is loaded from L2.
266
+ If L2 has keys {0, 1, 3, 4} but not key 2, only keys {0, 1} are
267
+ loaded because the gap at index 2 breaks the prefix. Keys beyond
268
+ the prefix are never transferred, saving I/O bandwidth and L1
269
+ memory. Use :meth:`query_prefetch_result` to retrieve the number
270
+ of prefix hits once the request completes.
271
+
272
+ Args:
273
+ keys: List of object keys to prefetch from L2 into L1.
274
+ The ordering defines the prefix: index 0 is the first key.
275
+ layout_desc: Memory layout for L1 write buffer allocation.
276
+ extra_count: Extra read locks per key (on top of the default 1)
277
+ to acquire when transitioning loaded keys from write-locked
278
+ to read-locked. Must match the ``extra_count`` used in the
279
+ corresponding ``submit_prefetch_task`` call so that all TP
280
+ workers can each consume one read lock.
281
+
282
+ Returns:
283
+ A request ID for tracking via query_prefetch_result.
284
+ """
285
+ with self._submission_lock:
286
+ request_id = self._next_request_id
287
+ self._next_request_id += 1
288
+ self._submission_queue.append((request_id, keys, layout_desc, extra_count))
289
+ os.eventfd_write(self._submission_efd, 1)
290
+ return request_id
291
+
292
+ def query_lookup_result(self, request_id: PrefetchRequestId) -> int | None:
293
+ """
294
+ Query the number of prefix hits from the lookup phase.
295
+
296
+ Thread-safe. Returns the number of prefix hits if the lookup phase
297
+ has completed, None if still in progress, or the prefetch request
298
+ has already been consumed by query_prefetch_result.
299
+
300
+ Args:
301
+ request_id: The request ID from submit_prefetch_request.
302
+
303
+ Returns:
304
+ Number of prefix hits from the lookup phase, or None if not yet complete
305
+ or if the request has already been consumed by a previous call to this
306
+ method.
307
+
308
+ Note:
309
+ This function does not pop the result. The caller need to make sure to call
310
+ the query_prefetch_result after calling this function, otherwise nobody
311
+ will clean up the completed lookups dictionary, causing memory leak.
312
+ """
313
+ with self._lookup_results_lock:
314
+ return self._completed_lookups.get(request_id, None)
315
+
316
+ def query_prefetch_result(self, request_id: PrefetchRequestId) -> int | None:
317
+ """
318
+ Query the result of a prefetch request.
319
+
320
+ Thread-safe. Returns the number of prefix hits if the request
321
+ has completed, None if still in progress. Each result can only
322
+ be retrieved once (subsequent calls return None).
323
+
324
+ Args:
325
+ request_id: The request ID from submit_prefetch_request.
326
+
327
+ Returns:
328
+ Number of prefix hits, or None if not yet complete.
329
+
330
+ Note:
331
+ This function will pop the completed lookup results as well.
332
+ Therefore, the caller need to make sure that never call
333
+ query_lookup_result after calling this function, otherwise it will
334
+ get None forever.
335
+ """
336
+ with self._prefetch_results_lock:
337
+ result = self._completed_results.pop(request_id, None)
338
+ if result is not None:
339
+ with self._lookup_results_lock:
340
+ self._completed_lookups.pop(request_id, None)
341
+ return result
342
+
343
+ def report_status(self) -> dict:
344
+ """Return a status dict for the prefetch controller."""
345
+ is_healthy = self._thread.is_alive()
346
+ with self._submission_lock:
347
+ submission_queue_size = len(self._submission_queue)
348
+ with self._prefetch_results_lock:
349
+ completed_results_count = len(self._completed_results)
350
+ return {
351
+ "is_healthy": is_healthy,
352
+ "thread_alive": is_healthy,
353
+ "max_in_flight": self._max_in_flight,
354
+ "submission_queue_size": submission_queue_size,
355
+ "pending_queue_size": self._status_pending_count,
356
+ "in_flight_request_count": self._status_in_flight_count,
357
+ "lookup_phase_count": self._status_lookup_phase_count,
358
+ "load_phase_count": self._status_load_phase_count,
359
+ "completed_results_count": completed_results_count,
360
+ "num_l2_adapters": len(self._l2_adapters),
361
+ }
362
+
363
+ # =========================================================================
364
+ # Lifecycle
365
+ # =========================================================================
366
+
367
+ def start(self) -> None:
368
+ """Start the background prefetch loop thread."""
369
+ logger.info("Starting PrefetchController...")
370
+ self._thread.start()
371
+
372
+ def stop(self) -> None:
373
+ """
374
+ Signal the loop to stop and wait for the thread to join.
375
+
376
+ Cleans up any in-flight requests (releases L1 write locks,
377
+ L2 locks) before returning.
378
+ """
379
+ self._stop_flag.set()
380
+ os.eventfd_write(self._submission_efd, 1)
381
+ self._thread.join()
382
+ self._cleanup_in_flight_requests()
383
+ os.close(self._submission_efd)
384
+
385
+ # =========================================================================
386
+ # Background loop
387
+ # =========================================================================
388
+
389
+ def _prefetch_loop(self) -> None:
390
+ """
391
+ Main event-driven loop running in a background thread.
392
+
393
+ Uses select.poll() to wait on:
394
+ - The submission eventfd (new prefetch requests).
395
+ - Each L2 adapter's lookup eventfd (completed lookups).
396
+ - Each L2 adapter's load eventfd (completed loads).
397
+ """
398
+ poller = select.poll()
399
+ poller.register(self._submission_efd, select.POLLIN)
400
+ for efd in self._lookup_efd_to_adapter:
401
+ poller.register(efd, select.POLLIN)
402
+ for efd in self._load_efd_to_adapter:
403
+ poller.register(efd, select.POLLIN)
404
+
405
+ while not self._stop_flag.is_set():
406
+ ready = poller.poll(PREFETCH_LOOP_POLL_TIMEOUT_MS)
407
+
408
+ for fd, events in ready:
409
+ if not (events & select.POLLIN):
410
+ continue
411
+
412
+ try:
413
+ os.eventfd_read(fd)
414
+ except (OSError, BlockingIOError):
415
+ pass
416
+
417
+ try:
418
+ if fd == self._submission_efd:
419
+ self._drain_submission_queue()
420
+ elif fd in self._lookup_efd_to_adapter:
421
+ self._process_lookup_completions(
422
+ self._lookup_efd_to_adapter[fd]
423
+ )
424
+ elif fd in self._load_efd_to_adapter:
425
+ self._process_load_completions(self._load_efd_to_adapter[fd])
426
+ except Exception:
427
+ logger.exception(
428
+ "Unexpected error in prefetch loop while processing fd %d",
429
+ fd,
430
+ )
431
+
432
+ try:
433
+ self._start_pending_requests()
434
+ except Exception:
435
+ logger.exception(
436
+ "Unexpected error in prefetch loop while starting pending requests"
437
+ )
438
+
439
+ def _drain_submission_queue(self) -> None:
440
+ """Move items from the thread-safe submission queue to the
441
+ pending queue."""
442
+ with self._submission_lock:
443
+ items = self._submission_queue
444
+ self._submission_queue = []
445
+ self._pending_queue.extend(items)
446
+ self._status_pending_count += len(items)
447
+
448
+ def _start_pending_requests(self) -> None:
449
+ """Start pending requests up to the max in-flight limit."""
450
+ while (
451
+ self._pending_queue and len(self._in_flight_requests) < self._max_in_flight
452
+ ):
453
+ request_id, keys, layout_desc, extra_count = self._pending_queue.pop(0)
454
+ self._status_pending_count -= 1
455
+ self._start_lookup_phase(request_id, keys, layout_desc, extra_count)
456
+
457
+ # =========================================================================
458
+ # Lookup phase
459
+ # =========================================================================
460
+
461
+ def _start_lookup_phase(
462
+ self,
463
+ request_id: PrefetchRequestId,
464
+ keys: list[ObjectKey],
465
+ layout_desc: MemoryLayoutDesc,
466
+ extra_count: int = 0,
467
+ ) -> None:
468
+ """Submit lookup_and_lock to all adapters for a new request."""
469
+ if not self._l2_adapters:
470
+ self._complete_request(request_id, 0)
471
+ return
472
+
473
+ pending_lookup_tasks: dict[int, L2TaskId] = {}
474
+ for i, adapter in enumerate(self._l2_adapters):
475
+ task_id = adapter.submit_lookup_and_lock_task(keys)
476
+ pending_lookup_tasks[i] = task_id
477
+
478
+ request = InFlightPrefetchRequest(
479
+ request_id=request_id,
480
+ keys=keys,
481
+ layout_desc=layout_desc,
482
+ phase=PrefetchPhase.LOOKUP,
483
+ extra_count=extra_count,
484
+ pending_lookup_tasks=pending_lookup_tasks,
485
+ )
486
+ self._in_flight_requests[request_id] = request
487
+ self._status_in_flight_count += 1
488
+ self._status_lookup_phase_count += 1
489
+
490
+ self._event_bus.publish(
491
+ Event(
492
+ event_type=EventType.L2_PREFETCH_LOOKUP_SUBMITTED,
493
+ metadata={
494
+ "request_id": request_id,
495
+ "key_count": len(keys),
496
+ "adapter_count": len(pending_lookup_tasks),
497
+ },
498
+ )
499
+ )
500
+
501
+ def _process_lookup_completions(self, adapter_index: int) -> None:
502
+ """Check all LOOKUP-phase requests for completed lookups from
503
+ this adapter."""
504
+ ready_to_transition: list[InFlightPrefetchRequest] = []
505
+
506
+ for request in list(self._in_flight_requests.values()):
507
+ if request.phase != PrefetchPhase.LOOKUP:
508
+ continue
509
+ if adapter_index not in request.pending_lookup_tasks:
510
+ continue
511
+
512
+ task_id = request.pending_lookup_tasks[adapter_index]
513
+ result = self._l2_adapters[adapter_index].query_lookup_and_lock_result(
514
+ task_id
515
+ )
516
+
517
+ if result is not None:
518
+ request.lookup_results[adapter_index] = result
519
+ del request.pending_lookup_tasks[adapter_index]
520
+
521
+ if request.all_lookups_done():
522
+ ready_to_transition.append(request)
523
+
524
+ for request in ready_to_transition:
525
+ self._transition_to_load_phase(request)
526
+
527
+ # =========================================================================
528
+ # Load phase
529
+ # =========================================================================
530
+ def _transition_to_load_phase(self, request: InFlightPrefetchRequest) -> None:
531
+ """Compute load plan, reserve L1 buffers, and submit load tasks."""
532
+ request.phase = PrefetchPhase.PLAN_AND_LOAD
533
+ self._status_lookup_phase_count -= 1
534
+ self._status_load_phase_count += 1
535
+
536
+ # Step 1: get load plan from policy
537
+ load_plan = self._policy.select_load_plan(
538
+ request.keys,
539
+ request.lookup_results,
540
+ self._adapter_descriptors,
541
+ )
542
+
543
+ # Step 2: trim the load plan to only prefix
544
+ trimmed_plan = trim_load_plan_to_prefix(load_plan, len(request.keys))
545
+
546
+ if not trimmed_plan:
547
+ # Nothing to load after trimming to prefix. Unlock all lookup locks
548
+ # and complete with 0 hits.
549
+ self._unlock_all_lookups(request)
550
+ self._update_lookup_results(request.request_id, 0)
551
+ self._event_bus.publish(
552
+ Event(
553
+ event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
554
+ metadata={
555
+ "request_id": request.request_id,
556
+ "prefix_hit_count": 0,
557
+ },
558
+ )
559
+ )
560
+ self._complete_request(request.request_id, 0)
561
+ return
562
+
563
+ # Step 3: reserve L1 write buffers
564
+ merged_bitmap = merge_bitmaps(trimmed_plan.values(), len(request.keys))
565
+ keys_to_reserve = merged_bitmap.gather(request.keys)
566
+ l1_mgr = self._l1_manager
567
+
568
+ retentions = self._policy.select_l1_retentions(
569
+ keys_to_reserve,
570
+ )
571
+ write_results = l1_mgr.reserve_write(
572
+ keys=keys_to_reserve,
573
+ is_temporary=[not r for r in retentions],
574
+ layout_desc=request.layout_desc,
575
+ mode="new",
576
+ )
577
+
578
+ # Step 4: filter to successfully reserved keys
579
+ reserved_key_set: set[ObjectKey] = set()
580
+ for key, (err, mem_obj) in write_results.items():
581
+ if err == L1Error.SUCCESS and mem_obj is not None:
582
+ request.write_reserved_keys.append(key)
583
+ request.write_reserved_objs[key] = mem_obj
584
+ reserved_key_set.add(key)
585
+ else:
586
+ logger.debug(
587
+ "Prefetch request %d: reserve write failed for %s: %s",
588
+ request.request_id,
589
+ key,
590
+ err,
591
+ )
592
+
593
+ # Step 5: recompute load plan excluding failed reservations
594
+ reserved_bitmap = Bitmap(len(request.keys))
595
+ for i, key in enumerate(request.keys):
596
+ if key in reserved_key_set:
597
+ reserved_bitmap.set(i)
598
+
599
+ prefix_length = reserved_bitmap.count_leading_ones()
600
+ trimmed_plan = trim_load_plan_to_first_n_keys(
601
+ load_plan, len(request.keys), prefix_length
602
+ )
603
+ request.load_plan = trimmed_plan
604
+
605
+ ## Step 6: phase 1 unlock — keys locked in lookup but not in plan
606
+ self._unlock_unneeded_keys(request)
607
+
608
+ if not trimmed_plan:
609
+ # Nothing loadable after filtering
610
+ if request.write_reserved_keys:
611
+ l1_mgr.finish_write(request.write_reserved_keys)
612
+ l1_mgr.delete(request.write_reserved_keys)
613
+ self._update_lookup_results(request.request_id, 0)
614
+ self._event_bus.publish(
615
+ Event(
616
+ event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
617
+ metadata={
618
+ "request_id": request.request_id,
619
+ "prefix_hit_count": 0,
620
+ },
621
+ )
622
+ )
623
+ self._complete_request(request.request_id, 0)
624
+ return
625
+
626
+ ## Step 7: submit load tasks per adapter
627
+ for adapter_idx, bitmap in trimmed_plan.items():
628
+ per_adapter_keys = bitmap.gather(request.keys)
629
+ per_adapter_objs = [
630
+ request.write_reserved_objs[key] for key in per_adapter_keys
631
+ ]
632
+ task_id = self._l2_adapters[adapter_idx].submit_load_task(
633
+ per_adapter_keys, per_adapter_objs
634
+ )
635
+ request.pending_load_tasks[adapter_idx] = task_id
636
+
637
+ ## Step 8: update the lookup result based on the final load plan
638
+ self._update_lookup_results(request.request_id, prefix_length)
639
+
640
+ self._event_bus.publish(
641
+ Event(
642
+ event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
643
+ metadata={
644
+ "request_id": request.request_id,
645
+ "prefix_hit_count": prefix_length,
646
+ },
647
+ )
648
+ )
649
+ self._event_bus.publish(
650
+ Event(
651
+ event_type=EventType.L2_PREFETCH_LOAD_SUBMITTED,
652
+ metadata={
653
+ "request_id": request.request_id,
654
+ "key_count": len(reserved_key_set),
655
+ "adapter_count": len(trimmed_plan),
656
+ },
657
+ )
658
+ )
659
+
660
+ logger.debug(
661
+ "Prefetch request %d: submitted load tasks to %d adapters for %d keys",
662
+ request.request_id,
663
+ len(trimmed_plan),
664
+ len(reserved_key_set),
665
+ )
666
+
667
+ def _update_lookup_results(
668
+ self, request_id: PrefetchRequestId, hit_chunks: int
669
+ ) -> None:
670
+ """Update the completed lookups dict with the number of prefix hits."""
671
+ with self._lookup_results_lock:
672
+ self._completed_lookups[request_id] = hit_chunks
673
+
674
+ def _process_load_completions(self, adapter_index: int) -> None:
675
+ """Check all PLAN_AND_LOAD-phase requests for completed loads."""
676
+ ready_to_finalize: list[InFlightPrefetchRequest] = []
677
+
678
+ for request in list(self._in_flight_requests.values()):
679
+ if request.phase != PrefetchPhase.PLAN_AND_LOAD:
680
+ continue
681
+ if adapter_index not in request.pending_load_tasks:
682
+ continue
683
+
684
+ task_id = request.pending_load_tasks[adapter_index]
685
+ result = self._l2_adapters[adapter_index].query_load_result(task_id)
686
+
687
+ if result is not None:
688
+ request.load_results[adapter_index] = result
689
+ del request.pending_load_tasks[adapter_index]
690
+
691
+ if request.all_loads_done():
692
+ ready_to_finalize.append(request)
693
+
694
+ for request in ready_to_finalize:
695
+ self._finalize_load(request)
696
+
697
+ def _finalize_load(self, request: InFlightPrefetchRequest) -> None:
698
+ """
699
+ Finalize a completed load: build result bitmap, transition L1
700
+ state, release non-prefix read locks, and report prefix hits.
701
+
702
+ Only prefix keys are submitted for loading, but partial load
703
+ failures can create gaps. Keys beyond the gap that were
704
+ successfully loaded still need their read locks released.
705
+ """
706
+ num_keys = len(request.keys)
707
+
708
+ # Scatter per-adapter local load results into global positions.
709
+ # Each adapter's load bitmap is locally indexed (size == adapter's
710
+ # key count). The plan bitmap maps local → global indices via
711
+ # get_indices_list().
712
+ result_bitmap = Bitmap(num_keys)
713
+ for adapter_idx, plan_bitmap in request.load_plan.items():
714
+ load_bitmap = request.load_results.get(adapter_idx)
715
+ if load_bitmap is None:
716
+ continue
717
+ plan_indices = plan_bitmap.get_indices_list()
718
+ for global_i in load_bitmap.gather(plan_indices):
719
+ result_bitmap.set(global_i)
720
+
721
+ # Separate loaded vs. failed among write-reserved keys
722
+ loaded_keys: list[ObjectKey] = result_bitmap.gather(request.keys)
723
+ loaded_set = set(loaded_keys)
724
+ failed_keys = [k for k in request.write_reserved_keys if k not in loaded_set]
725
+
726
+ # Phase 2 unlock: release L2 locks for all keys in the load plan
727
+ self._unlock_all_plan_keys(request)
728
+
729
+ l1_mgr = self._l1_manager
730
+
731
+ # Transition loaded keys: write-locked -> read-locked
732
+ # Use extra_count so that all TP workers each get their own read lock.
733
+ if loaded_keys:
734
+ l1_mgr.finish_write_and_reserve_read(
735
+ loaded_keys, extra_count=request.extra_count
736
+ )
737
+
738
+ # Clean up failed keys
739
+ if failed_keys:
740
+ l1_mgr.finish_write(failed_keys)
741
+ l1_mgr.delete(failed_keys)
742
+
743
+ self._event_bus.publish(
744
+ Event(
745
+ event_type=EventType.L2_PREFETCH_LOAD_COMPLETED,
746
+ metadata={
747
+ "request_id": request.request_id,
748
+ "loaded_count": len(loaded_keys),
749
+ "failed_count": len(failed_keys),
750
+ },
751
+ )
752
+ )
753
+
754
+ # Partial load failures can create gaps in the prefix.
755
+ # Release read locks for loaded keys beyond the prefix.
756
+ prefix_hits = result_bitmap.count_leading_ones()
757
+ prefix_mask = Bitmap(num_keys, prefix_hits)
758
+ non_prefix_loaded_bitmap = result_bitmap & (~prefix_mask)
759
+ non_prefix_loaded = non_prefix_loaded_bitmap.gather(request.keys)
760
+ if non_prefix_loaded:
761
+ l1_mgr.finish_read(non_prefix_loaded, extra_count=request.extra_count)
762
+
763
+ self._complete_request(request.request_id, prefix_hits)
764
+
765
+ # =========================================================================
766
+ # Unlock helpers
767
+ # =========================================================================
768
+
769
+ def _unlock_unneeded_keys(self, request: InFlightPrefetchRequest) -> None:
770
+ """Phase 1 unlock: keys locked in lookup but not in the load plan."""
771
+ for adapter_idx, lookup_bitmap in request.lookup_results.items():
772
+ plan_bitmap = request.load_plan.get(adapter_idx, Bitmap(len(request.keys)))
773
+ to_unlock_bitmap = lookup_bitmap & (~plan_bitmap)
774
+ unlock_keys = to_unlock_bitmap.gather(request.keys)
775
+ if unlock_keys:
776
+ self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
777
+
778
+ def _unlock_all_plan_keys(self, request: InFlightPrefetchRequest) -> None:
779
+ """Phase 2 unlock: release L2 locks for all keys in the load plan."""
780
+ for adapter_idx, load_bitmap in request.load_plan.items():
781
+ unlock_keys = load_bitmap.gather(request.keys)
782
+ self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
783
+
784
+ def _unlock_all_lookups(self, request: InFlightPrefetchRequest) -> None:
785
+ """Unlock all keys locked during lookup (nothing to load case)."""
786
+ for adapter_idx, lookup_bitmap in request.lookup_results.items():
787
+ unlock_keys = lookup_bitmap.gather(request.keys)
788
+ if unlock_keys:
789
+ self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
790
+
791
+ # =========================================================================
792
+ # Completion and cleanup
793
+ # =========================================================================
794
+
795
+ def _complete_request(
796
+ self, request_id: PrefetchRequestId, prefix_hits: int
797
+ ) -> None:
798
+ """Store the result and remove from in-flight tracking."""
799
+ with self._prefetch_results_lock:
800
+ self._completed_results[request_id] = prefix_hits
801
+ removed = self._in_flight_requests.pop(request_id, None)
802
+ if removed is not None:
803
+ self._status_in_flight_count -= 1
804
+ if removed.phase == PrefetchPhase.LOOKUP:
805
+ self._status_lookup_phase_count -= 1
806
+ elif removed.phase == PrefetchPhase.PLAN_AND_LOAD:
807
+ self._status_load_phase_count -= 1
808
+ logger.debug(
809
+ "Prefetch request %d completed: %d prefix hits",
810
+ request_id,
811
+ prefix_hits,
812
+ )
813
+
814
+ def _cleanup_in_flight_requests(self) -> None:
815
+ """Release resources for any in-flight requests during shutdown."""
816
+ l1_mgr = self._l1_manager
817
+ for request in self._in_flight_requests.values():
818
+ if request.phase == PrefetchPhase.PLAN_AND_LOAD:
819
+ if request.write_reserved_keys:
820
+ l1_mgr.finish_write(request.write_reserved_keys)
821
+ l1_mgr.delete(request.write_reserved_keys)
822
+ self._unlock_all_plan_keys(request)
823
+ elif request.phase == PrefetchPhase.LOOKUP:
824
+ self._unlock_all_lookups(request)
825
+ logger.warning(
826
+ "Cleaning up in-flight prefetch request %d (%d keys).",
827
+ request.request_id,
828
+ len(request.keys),
829
+ )
830
+ self._in_flight_requests.clear()