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,679 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import Callable, NamedTuple, Optional, Set
6
+ import threading
7
+
8
+ # Third Party
9
+ import zmq.asyncio
10
+
11
+ # First Party
12
+ from lmcache.logging import init_logger
13
+ from lmcache.v1.cache_controller.locks import FastLockWithTimeout, RWLockWithTimeout
14
+ from lmcache.v1.cache_controller.message import BatchedKVOperationMsg, WorkerInfo
15
+ from lmcache.v1.utils.cache_utils import TTLListCache
16
+
17
+ logger = init_logger(__name__)
18
+
19
+
20
+ class KVChunkInfo(NamedTuple):
21
+ """
22
+ Represents the location information of a KV chunk in the cluster.
23
+ This class is immutable and can be used as a dictionary key.
24
+ """
25
+
26
+ instance_id: str
27
+ worker_id: int
28
+ location: str
29
+
30
+
31
+ class FullSyncState(Enum):
32
+ """State of full sync for a worker"""
33
+
34
+ IDLE = "idle" # Not in sync
35
+ SYNCING = "syncing" # Sync in progress
36
+ COMPLETED = "completed" # Sync completed
37
+ FAILED = "failed" # Sync failed/timeout
38
+
39
+
40
+ @dataclass
41
+ class WorkerSyncInfo:
42
+ """Information about a worker's sync state"""
43
+
44
+ sync_id: str
45
+ state: FullSyncState
46
+ start_time: float
47
+ expected_total_keys: int
48
+ expected_batch_count: int
49
+ received_batches: Set[int] = field(default_factory=set)
50
+ received_keys_count: int = 0
51
+ last_activity_time: float = 0.0
52
+
53
+ def __post_init__(self):
54
+ if self.last_activity_time == 0.0:
55
+ self.last_activity_time = self.start_time
56
+
57
+
58
+ @dataclass
59
+ class WorkerNode:
60
+ """
61
+ Represents a single worker with all its associated metadata.
62
+ """
63
+
64
+ worker_id: int
65
+ ip: str
66
+ port: int
67
+ peer_init_url: Optional[str]
68
+ socket: Optional[zmq.asyncio.Socket]
69
+ registration_time: float
70
+ last_heartbeat_time: float
71
+ # Guarded by _lock
72
+ seq_tracker: dict[str, int] = field(default_factory=dict) # location -> seq_num
73
+ kv_store: dict[str, set[int]] = field(
74
+ default_factory=dict
75
+ ) # location -> set[chunk_hash]
76
+ sync_info: Optional[WorkerSyncInfo] = None # Full sync state
77
+
78
+ def __post_init__(self):
79
+ # Fast lock with timeout for WorkerNode operations
80
+ self._lock = FastLockWithTimeout()
81
+
82
+ def handle_batched_kv_operations(
83
+ self,
84
+ msg: BatchedKVOperationMsg,
85
+ on_seq_num_out_of_order: Optional[Callable[[], None]] = None,
86
+ is_full_sync: bool = False,
87
+ ) -> bool:
88
+ """
89
+ Handle batched KV operations with single lock acquisition.
90
+ Logs warning and calls callback if sequence out of order is detected.
91
+
92
+ Args:
93
+ msg: The batched KV operation message.
94
+ on_seq_num_out_of_order: Callback when sequence out of order detected.
95
+ is_full_sync: If True, this is a full sync batch operation.
96
+ During full sync, sequence check is skipped.
97
+ When worker is syncing, non-full-sync operations are rejected.
98
+
99
+ Returns:
100
+ True if operations were processed, False if rejected
101
+ (e.g., during syncing when is_full_sync=False).
102
+ """
103
+ seq_warning: Optional[tuple[int, int, int]] = None
104
+ with self._lock:
105
+ # During syncing, reject non-full-sync operations
106
+ if (
107
+ self.sync_info is not None
108
+ and self.sync_info.state == FullSyncState.SYNCING
109
+ and not is_full_sync
110
+ ):
111
+ return False
112
+
113
+ location = msg.location
114
+
115
+ if location not in self.kv_store:
116
+ self.kv_store[location] = set()
117
+
118
+ for op in msg.operations:
119
+ # Apply operation first, then check sequence
120
+ # (sequence check is skipped during full sync)
121
+ if op.op_type.value == "admit":
122
+ self.kv_store[location].add(op.key)
123
+ elif op.op_type.value == "evict":
124
+ self.kv_store[location].discard(op.key)
125
+ else:
126
+ logger.error(f"Unknown op_type: {op.op_type}")
127
+
128
+ # Skip sequence check during full sync
129
+ if is_full_sync:
130
+ continue
131
+
132
+ # Sequence check
133
+ last_seq_num = self.seq_tracker.get(location)
134
+ if last_seq_num is not None:
135
+ expected_seq = last_seq_num + 1
136
+ if op.seq_num != expected_seq and seq_warning is None:
137
+ seq_warning = (
138
+ expected_seq,
139
+ op.seq_num,
140
+ op.seq_num - expected_seq,
141
+ )
142
+ self.seq_tracker[location] = op.seq_num
143
+
144
+ # Clean up empty set
145
+ if not self.kv_store[location]:
146
+ self.kv_store.pop(location, None)
147
+
148
+ # Log warning and call callback outside lock
149
+ if seq_warning is not None:
150
+ if on_seq_num_out_of_order is not None:
151
+ on_seq_num_out_of_order()
152
+ logger.warning(
153
+ "KV batch sequence out of order detected: "
154
+ "key=%s, expected_seq=%s, actual_seq=%s, gap=%s",
155
+ (msg.instance_id, msg.worker_id, msg.location),
156
+ seq_warning[0],
157
+ seq_warning[1],
158
+ seq_warning[2],
159
+ )
160
+ return True
161
+
162
+ def has_kv(self, location: str, key: int) -> bool:
163
+ """Check if a KV chunk exists in this worker."""
164
+ with self._lock:
165
+ return location in self.kv_store and key in self.kv_store[location]
166
+
167
+ def get_kv_keys(self, location: str) -> set[int]:
168
+ """Get all keys for a location."""
169
+ with self._lock:
170
+ keys = self.kv_store.get(location)
171
+ if keys is None:
172
+ return set()
173
+ # Return a shallow copy for thread safety
174
+ return set(keys)
175
+
176
+ def clear_kv_store(self) -> None:
177
+ """Clear all KV data for this worker."""
178
+ with self._lock:
179
+ self.kv_store.clear()
180
+ self.seq_tracker.clear()
181
+
182
+ def get_kv_count(self) -> int:
183
+ """Get total count of KV chunks."""
184
+ with self._lock:
185
+ return sum(len(keys) for keys in self.kv_store.values())
186
+
187
+ def update_seq_num(self, location: str, seq_num: int) -> None:
188
+ """Update sequence number for a location."""
189
+ with self._lock:
190
+ self.seq_tracker[location] = seq_num
191
+
192
+ def get_seq_num(self, location: str) -> Optional[int]:
193
+ """Get sequence number for a location."""
194
+ with self._lock:
195
+ return self.seq_tracker.get(location)
196
+
197
+ def find_key(
198
+ self, key: int
199
+ ) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
200
+ """
201
+ Find a key in this worker's kv_store.
202
+ Returns: (KVChunkInfo, peer_init_url, keys) if found, None otherwise.
203
+ KVChunkInfo will have instance_id="" since WorkerNode doesn't know
204
+ its instance.
205
+ """
206
+ with self._lock:
207
+ for location, keys in self.kv_store.items():
208
+ if key in keys:
209
+ # WorkerNode doesn't know its instance_id, so we leave it empty
210
+ # The caller should fill in the instance_id
211
+ return (
212
+ KVChunkInfo("", self.worker_id, location),
213
+ self.peer_init_url,
214
+ keys,
215
+ )
216
+ return None
217
+
218
+ def find_key_simple(self, key: int) -> Optional[KVChunkInfo]:
219
+ """
220
+ Find a key in this worker's kv_store, returning only KVChunkInfo.
221
+ KVChunkInfo will have instance_id="" since WorkerNode doesn't know
222
+ its instance.
223
+ """
224
+ with self._lock:
225
+ for location, keys in self.kv_store.items():
226
+ if key in keys:
227
+ return KVChunkInfo("", self.worker_id, location)
228
+ return None
229
+
230
+ def to_worker_info(self, instance_id: str) -> WorkerInfo:
231
+ """Convert to WorkerInfo for backward compatibility."""
232
+ # No need to lock here
233
+ return WorkerInfo(
234
+ instance_id=instance_id,
235
+ worker_id=self.worker_id,
236
+ ip=self.ip,
237
+ port=self.port,
238
+ peer_init_url=self.peer_init_url,
239
+ registration_time=self.registration_time,
240
+ last_heartbeat_time=self.last_heartbeat_time,
241
+ )
242
+
243
+
244
+ @dataclass
245
+ class InstanceNode:
246
+ """
247
+ Represents an instance with all its workers.
248
+ Tree structure: InstanceNode -> WorkerNode
249
+ Each InstanceNode has its own lock for thread-safe worker operations.
250
+ """
251
+
252
+ instance_id: str
253
+ # Guarded by _rwlock
254
+ workers: dict[int, WorkerNode] = field(
255
+ default_factory=dict
256
+ ) # worker_id -> WorkerNode
257
+
258
+ def __post_init__(self):
259
+ # RW lock for protecting workers dict access
260
+ self._rwlock = RWLockWithTimeout()
261
+
262
+ def add_worker(self, worker_node: WorkerNode) -> None:
263
+ """Add a worker to this instance."""
264
+ with self._rwlock.write_lock(timeout=10):
265
+ self.workers[worker_node.worker_id] = worker_node
266
+
267
+ def remove_worker(self, worker_id: int) -> Optional[WorkerNode]:
268
+ """Remove and return a worker from this instance."""
269
+ with self._rwlock.write_lock(timeout=10):
270
+ return self.workers.pop(worker_id, None)
271
+
272
+ def get_worker(self, worker_id: int) -> Optional[WorkerNode]:
273
+ """Get a worker by worker_id. Uses optimistic locking (lock-free read)."""
274
+ # Optimistic read: workers dict rarely changes
275
+ return self.workers.get(worker_id)
276
+
277
+ def get_worker_ids(self) -> list[int]:
278
+ """Get sorted list of worker IDs."""
279
+ # Snapshot keys to avoid RuntimeError if dict changes during iteration
280
+ return sorted(list(self.workers.keys()))
281
+
282
+ def has_workers(self) -> bool:
283
+ """Check if instance has any workers."""
284
+ # Optimistic read: workers dict rarely changes
285
+ return len(self.workers) > 0
286
+
287
+ def get_all_worker_infos(self) -> list[WorkerInfo]:
288
+ """Get WorkerInfo for all workers in this instance."""
289
+ # Snapshot values to avoid RuntimeError if dict changes during iteration
290
+ workers_snapshot = list(self.workers.values())
291
+ return [worker.to_worker_info(self.instance_id) for worker in workers_snapshot]
292
+
293
+ def find_key(
294
+ self, key: int
295
+ ) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
296
+ """
297
+ Find a key in any worker within this instance.
298
+ Returns: (KVChunkInfo, peer_init_url, keys) if found, None otherwise.
299
+ """
300
+ # Snapshot workers to avoid RuntimeError if dict changes during iteration
301
+ workers_snapshot = list(self.workers.items())
302
+ for worker_id, worker_node in workers_snapshot:
303
+ if result := worker_node.find_key(key):
304
+ # Fill in the instance_id in KVChunkInfo
305
+ kv_info, peer_init_url, keys = result
306
+ return (
307
+ KVChunkInfo(self.instance_id, worker_id, kv_info.location),
308
+ peer_init_url,
309
+ keys,
310
+ )
311
+ return None
312
+
313
+ def find_key_simple(self, key: int) -> Optional[KVChunkInfo]:
314
+ """
315
+ Find a key in any worker within this instance, returning only KVChunkInfo.
316
+ """
317
+ # Snapshot workers to avoid RuntimeError if dict changes during iteration
318
+ workers_snapshot = list(self.workers.items())
319
+ for worker_id, worker_node in workers_snapshot:
320
+ if kv_info := worker_node.find_key_simple(key):
321
+ return KVChunkInfo(self.instance_id, worker_id, kv_info.location)
322
+ return None
323
+
324
+ def has_worker_with_ip(self, ip: str) -> bool:
325
+ """
326
+ Check if any worker in this instance has the specified IP address.
327
+ """
328
+ # Snapshot workers to avoid RuntimeError if dict changes during iteration
329
+ return any(worker.ip == ip for worker in list(self.workers.values()))
330
+
331
+ def get_total_kv_count(self) -> int:
332
+ """
333
+ Get total count of KV chunks across all workers in this instance.
334
+ """
335
+ # Snapshot workers to avoid RuntimeError if dict changes during iteration
336
+ return sum(worker.get_kv_count() for worker in list(self.workers.values()))
337
+
338
+
339
+ class RegistryTree:
340
+ """
341
+ Central registry managing the tree structure of instances and workers.
342
+ Structure: instance_id -> InstanceNode -> WorkerNode
343
+
344
+ Lock hierarchy (from coarse to fine):
345
+ 1. RegistryTree._rwlock: protects instances dict access
346
+ 2. InstanceNode._rwlock: protects workers dict access
347
+ 3. WorkerNode._lock: protects kv_store and seq_tracker access
348
+
349
+ This fine-grained locking allows concurrent operations on different
350
+ instances/workers, improving throughput significantly.
351
+ """
352
+
353
+ def __init__(self):
354
+ # Guarded by _rwlock
355
+ # instance_id -> InstanceNode
356
+ self.instances: dict[str, InstanceNode] = {}
357
+ # RW lock only for protecting instances dict access
358
+ self._rwlock = RWLockWithTimeout()
359
+ # Atomic counter for sequence discontinuity (protected by _counter_lock)
360
+ self._seq_discontinuity_count = 0
361
+ self._counter_lock = threading.Lock()
362
+ # Cache for get_all_worker_infos (for Prometheus metrics)
363
+ self._worker_info_cache: TTLListCache[WorkerInfo] = TTLListCache[WorkerInfo]()
364
+ # Cache for get_all_worker_nodes (for FullSyncTracker and other use cases)
365
+ self._worker_node_cache: TTLListCache[tuple[str, WorkerNode]] = TTLListCache[
366
+ tuple[str, WorkerNode]
367
+ ]()
368
+
369
+ def get_seq_discontinuity_count(self) -> int:
370
+ """Get the count of sequence discontinuities (thread-safe)."""
371
+ # Lock-free read, no need for lock
372
+ return self._seq_discontinuity_count
373
+
374
+ def _incr_seq_discontinuity_count(self) -> None:
375
+ """Increment the sequence discontinuity counter (thread-safe)."""
376
+ with self._counter_lock:
377
+ self._seq_discontinuity_count += 1
378
+
379
+ def _get_or_create_instance(self, instance_id: str) -> InstanceNode:
380
+ """Get or create an instance node. Internal use only."""
381
+ # Optimistic read first: instances dict rarely changes
382
+ instance_node = self.instances.get(instance_id)
383
+ if instance_node is not None:
384
+ return instance_node
385
+
386
+ # Need to create, use write lock
387
+ with self._rwlock.write_lock(timeout=10):
388
+ # Double-check after acquiring write lock
389
+ instance_node = self.instances.get(instance_id)
390
+ if instance_node is None:
391
+ instance_node = InstanceNode(instance_id=instance_id)
392
+ self.instances[instance_id] = instance_node
393
+ return instance_node
394
+
395
+ def _get_instance(self, instance_id: str) -> Optional[InstanceNode]:
396
+ """Get an instance node. Uses optimistic locking (lock-free read)."""
397
+ # Optimistic read: instances dict rarely changes
398
+ return self.instances.get(instance_id)
399
+
400
+ def register_worker(
401
+ self,
402
+ instance_id: str,
403
+ worker_id: int,
404
+ ip: str,
405
+ port: int,
406
+ peer_init_url: Optional[str],
407
+ socket: zmq.asyncio.Socket,
408
+ registration_time: float,
409
+ ) -> WorkerNode:
410
+ """Register a new worker, creating instance if needed."""
411
+ # Get or create instance (locks instances dict)
412
+ instance_node = self._get_or_create_instance(instance_id)
413
+
414
+ # Create worker node
415
+ worker_node = WorkerNode(
416
+ worker_id=worker_id,
417
+ ip=ip,
418
+ port=port,
419
+ peer_init_url=peer_init_url,
420
+ socket=socket,
421
+ registration_time=registration_time,
422
+ last_heartbeat_time=registration_time,
423
+ )
424
+ # Add worker (locks workers dict in instance_node)
425
+ instance_node.add_worker(worker_node)
426
+ return worker_node
427
+
428
+ def deregister_worker(
429
+ self, instance_id: str, worker_id: int
430
+ ) -> Optional[WorkerNode]:
431
+ """Deregister a worker and clean up empty instances."""
432
+ instance_node = self._get_instance(instance_id)
433
+ if instance_node is None:
434
+ return None
435
+
436
+ # Remove worker (locks workers dict in instance_node)
437
+ worker_node = instance_node.remove_worker(worker_id)
438
+
439
+ # Clean up empty instance (need write lock on instances)
440
+ if not instance_node.has_workers():
441
+ # TODO(baoloongmao): Move timeout values to configuration
442
+ with self._rwlock.write_lock(timeout=100):
443
+ # Double-check after acquiring write lock
444
+ # Use pop to avoid KeyError if another thread already removed it
445
+ if not instance_node.has_workers() and instance_id in self.instances:
446
+ self.instances.pop(instance_id, None)
447
+
448
+ return worker_node
449
+
450
+ def get_worker(self, instance_id: str, worker_id: int) -> Optional[WorkerNode]:
451
+ """Get a specific worker."""
452
+ instance_node = self._get_instance(instance_id)
453
+ if instance_node is None:
454
+ return None
455
+ return instance_node.get_worker(worker_id)
456
+
457
+ def get_instance(self, instance_id: str) -> Optional[InstanceNode]:
458
+ """Get an instance by instance_id."""
459
+ return self._get_instance(instance_id)
460
+
461
+ def get_instance_by_ip(self, ip: str) -> Optional[InstanceNode]:
462
+ """Get an instance by IP address. Returns first instance if multiple exist."""
463
+ # Snapshot to avoid RuntimeError if dict changes during iteration
464
+ for instance_node in list(self.instances.values()):
465
+ if instance_node.has_worker_with_ip(ip):
466
+ return instance_node
467
+ return None
468
+
469
+ def get_instances_by_ip(self, ip: str) -> list[InstanceNode]:
470
+ """Get all instances by IP address."""
471
+ # Snapshot to avoid RuntimeError if dict changes during iteration
472
+ result = []
473
+ for instance_node in list(self.instances.values()):
474
+ if instance_node.has_worker_with_ip(ip):
475
+ result.append(instance_node)
476
+ return result
477
+
478
+ def get_worker_ids(self, instance_id: str) -> list[int]:
479
+ """Get sorted list of worker IDs for an instance."""
480
+ instance_node = self._get_instance(instance_id)
481
+ if instance_node is None:
482
+ return []
483
+ return instance_node.get_worker_ids()
484
+
485
+ def get_all_worker_infos_cached(
486
+ self, timeout_seconds: Optional[float] = None
487
+ ) -> list[WorkerInfo]:
488
+ """Get WorkerInfo for all workers across all instances.
489
+
490
+ Args:
491
+ timeout_seconds: Cache timeout in seconds.
492
+ - If None (default): use the cache's default timeout.
493
+ - If 0: cache immediately expires, always get fresh data
494
+ and update cache.
495
+ - If > 0: use cache with specified timeout.
496
+
497
+ Returns:
498
+ List of WorkerInfo, either from cache or fresh data
499
+ """
500
+ return self._worker_info_cache.get_cached(
501
+ lambda: self._get_all_worker_infos_fresh(), timeout_override=timeout_seconds
502
+ )
503
+
504
+ def _get_all_worker_infos_fresh(self) -> list[WorkerInfo]:
505
+ """Internal method to get fresh WorkerInfo without cache."""
506
+ # Snapshot to avoid RuntimeError if dict changes during iteration
507
+ result = []
508
+ for instance_node in list(self.instances.values()):
509
+ result.extend(instance_node.get_all_worker_infos())
510
+ return result
511
+
512
+ def get_all_worker_nodes_cached(
513
+ self, timeout_seconds: Optional[float] = None
514
+ ) -> list[tuple[str, WorkerNode]]:
515
+ """Get all (instance_id, WorkerNode) tuples across all instances, with caching.
516
+
517
+ Args:
518
+ timeout_seconds: Cache timeout in seconds.
519
+ - If None (default): use the cache's default timeout.
520
+ - If 0: cache immediately expires, always get fresh data
521
+ and update cache.
522
+ - If > 0: use cache with specified timeout.
523
+
524
+ Returns:
525
+ List of (instance_id, WorkerNode) tuples, either from cache or fresh data
526
+ """
527
+ return self._worker_node_cache.get_cached(
528
+ lambda: self._get_all_worker_nodes_fresh(), timeout_override=timeout_seconds
529
+ )
530
+
531
+ def _get_all_worker_nodes_fresh(self) -> list[tuple[str, WorkerNode]]:
532
+ """Internal method to get fresh (instance_id, WorkerNode) tuples
533
+ without cache."""
534
+ # Snapshot to avoid RuntimeError if dict changes during iteration
535
+ result = []
536
+ for instance_id, instance_node in self.instances.items():
537
+ for worker_node in instance_node.workers.values():
538
+ result.append((instance_id, worker_node))
539
+ return result
540
+
541
+ def update_heartbeat(
542
+ self, instance_id: str, worker_id: int, timestamp: float
543
+ ) -> bool:
544
+ """Update worker heartbeat timestamp. Returns True if successful."""
545
+ instance_node = self._get_instance(instance_id)
546
+ if instance_node is None:
547
+ return False
548
+ worker_node = instance_node.get_worker(worker_id)
549
+ if worker_node is None:
550
+ return False
551
+ worker_node.last_heartbeat_time = timestamp
552
+ return True
553
+
554
+ def handle_batched_kv_operations(
555
+ self, msg: BatchedKVOperationMsg, is_full_sync: bool = False
556
+ ) -> bool:
557
+ """
558
+ Handle batched KV operations by forwarding to WorkerNode.
559
+
560
+ Args:
561
+ msg: The batched KV operation message.
562
+ is_full_sync: If True, this is a full sync batch operation.
563
+
564
+ Returns:
565
+ True if operations were processed successfully.
566
+ False if worker not found or operations were rejected.
567
+ """
568
+ instance_node = self._get_instance(msg.instance_id)
569
+ if instance_node is None:
570
+ return False
571
+ worker_node = instance_node.get_worker(msg.worker_id)
572
+ if worker_node is None:
573
+ return False
574
+ return worker_node.handle_batched_kv_operations(
575
+ msg,
576
+ on_seq_num_out_of_order=self._incr_seq_discontinuity_count,
577
+ is_full_sync=is_full_sync,
578
+ )
579
+
580
+ def find_kv(
581
+ self,
582
+ key: int,
583
+ exclude_instance_id: Optional[str] = None,
584
+ ) -> Optional[KVChunkInfo]:
585
+ """
586
+ Find a KV chunk across all workers.
587
+
588
+ Args:
589
+ key: The KV chunk key to find.
590
+ exclude_instance_id: Instance ID to exclude
591
+ (all workers in this instance will be excluded).
592
+
593
+ Returns: KVChunkInfo if found, None otherwise.
594
+ """
595
+ with self._rwlock.read_lock(timeout=1):
596
+ for instance_id, instance_node in self.instances.items():
597
+ # Exclude all workers in the specified instance
598
+ if (
599
+ exclude_instance_id is not None
600
+ and instance_id == exclude_instance_id
601
+ ):
602
+ continue
603
+ result = instance_node.find_key_simple(key)
604
+ if result is not None:
605
+ return result
606
+ return None
607
+
608
+ def find_kv_with_worker_info(
609
+ self,
610
+ key: int,
611
+ exclude_instance_id: Optional[str] = None,
612
+ ) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
613
+ """
614
+ Find a KV chunk and return worker info in one lookup.
615
+ Optimized for batched_p2p_lookup to avoid multiple lookups.
616
+
617
+ Returns: (KVChunkInfo, peer_init_url, kv_keys) if found, None otherwise.
618
+ """
619
+ # Snapshot instances to avoid RuntimeError if dict changes during iteration
620
+ for instance_id, instance_node in list(self.instances.items()):
621
+ if exclude_instance_id is not None and instance_id == exclude_instance_id:
622
+ continue
623
+ result = instance_node.find_key(key)
624
+ if result is not None:
625
+ return result
626
+ return None
627
+
628
+ def get_total_kv_count(self) -> int:
629
+ """Get total count of KV chunks across all workers."""
630
+ # Snapshot to avoid RuntimeError if dict changes during iteration
631
+ total = 0
632
+ for instance_node in list(self.instances.values()):
633
+ total += instance_node.get_total_kv_count()
634
+ return total
635
+
636
+ def get_worker_kv_keys(
637
+ self, instance_id: str, worker_id: int, location: str
638
+ ) -> set[int]:
639
+ """Get all KV keys for a specific worker and location."""
640
+ instance_node = self._get_instance(instance_id)
641
+ if instance_node is None:
642
+ return set()
643
+ worker_node = instance_node.get_worker(worker_id)
644
+ if worker_node is None:
645
+ return set()
646
+ return worker_node.get_kv_keys(location)
647
+
648
+ def clear_worker_kv(
649
+ self, instance_id: str, worker_id: int, location: Optional[str] = None
650
+ ) -> bool:
651
+ """
652
+ Clear all KV chunks for a specific worker and location.
653
+ Returns True if successful, False if worker not found.
654
+ """
655
+ worker_node = self.get_worker(instance_id, worker_id)
656
+ if worker_node is None:
657
+ return False
658
+ with worker_node._lock:
659
+ if location is None:
660
+ worker_node.kv_store.clear()
661
+ worker_node.seq_tracker.clear()
662
+ return True
663
+ if location in worker_node.kv_store:
664
+ del worker_node.kv_store[location]
665
+ if location in worker_node.seq_tracker:
666
+ del worker_node.seq_tracker[location]
667
+ return True
668
+
669
+ def clear_all_worker_kv(self, instance_id: str, worker_id: int) -> bool:
670
+ """
671
+ Clear all KV chunks for a worker across all locations.
672
+ Returns True if successful, False if worker not found.
673
+ """
674
+ worker_node = self.get_worker(instance_id, worker_id)
675
+ if worker_node is None:
676
+ return False
677
+
678
+ worker_node.clear_kv_store()
679
+ return True