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,810 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from concurrent.futures import Future
4
+ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Sequence, Union
5
+ import threading
6
+ import time
7
+
8
+ # Third Party
9
+ import torch
10
+
11
+ # First Party
12
+ from lmcache.integration.vllm.utils import get_size_bytes
13
+ from lmcache.logging import init_logger
14
+ from lmcache.observability import LMCStatsMonitor, PrometheusLogger
15
+ from lmcache.utils import CacheEngineKey, _lmcache_nvtx_annotate
16
+ from lmcache.v1.cache_controller.message import OpType
17
+ from lmcache.v1.config import LMCacheEngineConfig
18
+ from lmcache.v1.memory_management import (
19
+ MemoryAllocatorInterface,
20
+ MemoryFormat,
21
+ MemoryObj,
22
+ MixedMemoryAllocator,
23
+ PagedCpuGpuMemoryAllocator,
24
+ )
25
+ from lmcache.v1.metadata import LMCacheMetadata
26
+ from lmcache.v1.storage_backend.abstract_backend import AllocatorBackendInterface
27
+ from lmcache.v1.storage_backend.batched_message_sender import BatchedMessageSender
28
+ from lmcache.v1.storage_backend.cache_policy import get_cache_policy
29
+ from lmcache.v1.system_detection import NUMADetector, SystemMemoryDetector
30
+
31
+ if TYPE_CHECKING:
32
+ # First Party
33
+ from lmcache.v1.cache_controller.worker import LMCacheWorker
34
+
35
+ logger = init_logger(__name__)
36
+
37
+
38
+ class LocalCPUBackend(AllocatorBackendInterface):
39
+ """
40
+ Even if local_cpu is False (the hot_cache is not used), contains(),
41
+ insert_key(), remove(), get_blocking(), get_keys(), and clear()
42
+ are still callable by the storage manager.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ config: LMCacheEngineConfig,
48
+ metadata: Optional[LMCacheMetadata] = None,
49
+ dst_device: str = "cuda",
50
+ lmcache_worker: Optional["LMCacheWorker"] = None,
51
+ memory_allocator: Optional[MemoryAllocatorInterface] = None,
52
+ ):
53
+ if torch.cuda.is_available():
54
+ super().__init__(dst_device)
55
+ else:
56
+ super().__init__("cpu")
57
+
58
+ self.cache_policy = get_cache_policy(config.cache_policy)
59
+ self.hot_cache = self.cache_policy.init_mutable_mapping()
60
+
61
+ self.use_hot = config.local_cpu
62
+ # NOTE: we keep the memory allocator argument for temporary
63
+ # test compatibility
64
+ # TODO: fix the tests to get rid the memory allocator
65
+ assert metadata is not None or memory_allocator is not None
66
+ self.memory_allocator = (
67
+ self.initialize_allocator(config, metadata) # type: ignore
68
+ if memory_allocator is None
69
+ else memory_allocator
70
+ )
71
+ self.lmcache_worker = lmcache_worker
72
+ self.instance_id = config.lmcache_instance_id
73
+ self.cpu_lock = threading.Lock()
74
+
75
+ self.stats_monitor = LMCStatsMonitor.GetOrCreate()
76
+
77
+ self.layerwise = config.use_layerwise
78
+ self.enable_blending = config.enable_blending
79
+
80
+ # Store config and metadata for chunk budget calculation
81
+ self.config = config
82
+ self.metadata = metadata
83
+
84
+ # to help maintain suffix -> prefix order in the dict
85
+ # assumption: only one request is looked up at a time
86
+ # (only one worker per cache engine)
87
+ self.keys_in_request: List[CacheEngineKey] = []
88
+
89
+ # Batched message sender for controller communication
90
+ self.batched_msg_sender: Optional[BatchedMessageSender] = None
91
+
92
+ # Initialize batched message sender
93
+ if lmcache_worker and metadata is not None:
94
+ self.batched_msg_sender = BatchedMessageSender(
95
+ metadata=metadata,
96
+ config=config,
97
+ location=str(self), # Backend location
98
+ lmcache_worker=lmcache_worker,
99
+ )
100
+ else:
101
+ logger.warning("Controller message sender is not initialized")
102
+
103
+ self._setup_metrics()
104
+
105
+ def _setup_metrics(self):
106
+ prometheus_logger = PrometheusLogger.GetInstanceOrNone()
107
+ if prometheus_logger is not None:
108
+ prometheus_logger.local_cpu_hot_cache_count.set_function(
109
+ lambda: len(self.hot_cache)
110
+ )
111
+ prometheus_logger.local_cpu_keys_in_request_count.set_function(
112
+ lambda: len(self.keys_in_request)
113
+ )
114
+
115
+ def __str__(self):
116
+ return self.__class__.__name__
117
+
118
+ def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
119
+ with self.cpu_lock:
120
+ if key not in self.hot_cache:
121
+ return False
122
+ if pin:
123
+ self.hot_cache[key].pin()
124
+ # vllm lookup sets pin to True
125
+ self.keys_in_request.append(key)
126
+ return True
127
+
128
+ def touch_cache(self):
129
+ # flip the order of the keys in the request
130
+ with self.cpu_lock:
131
+ for key in reversed(self.keys_in_request):
132
+ self.cache_policy.update_on_hit(key, self.hot_cache)
133
+ self.keys_in_request = []
134
+
135
+ def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
136
+ """
137
+ contains() and exists_in_put_tasks() should be checked together
138
+ """
139
+ return False
140
+
141
+ def submit_put_task(
142
+ self,
143
+ key: CacheEngineKey,
144
+ memory_obj: MemoryObj,
145
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
146
+ ) -> Optional[Future]:
147
+ """
148
+ Synchronously put the MemoryObj into the local cpu backend.
149
+
150
+ :param on_complete_callback: Optional callback invoked after the
151
+ synchronous put completes. Callback exceptions are caught and logged.
152
+ """
153
+ stored = False
154
+ with self.cpu_lock:
155
+ if key in self.hot_cache:
156
+ return None
157
+
158
+ memory_obj.ref_count_up()
159
+ self.hot_cache[key] = memory_obj
160
+
161
+ self.cache_policy.update_on_put(key)
162
+
163
+ # Push kv admit msg with batching
164
+ if self.batched_msg_sender is not None:
165
+ self.batched_msg_sender.add_kv_op(
166
+ op_type=OpType.ADMIT,
167
+ key=key.chunk_hash,
168
+ )
169
+ stored = True
170
+
171
+ # Call callback after put completes (outside lock)
172
+ if stored and on_complete_callback is not None:
173
+ try:
174
+ on_complete_callback(key)
175
+ except Exception as e:
176
+ logger.warning(f"on_complete_callback failed for key {key}: {e}")
177
+
178
+ return None
179
+
180
+ def batched_submit_put_task(
181
+ self,
182
+ keys: Sequence[CacheEngineKey],
183
+ memory_objs: List[MemoryObj],
184
+ transfer_spec: Any = None,
185
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
186
+ ) -> None:
187
+ """
188
+ Synchronously put the MemoryObjs into the local cpu backend.
189
+
190
+ :param on_complete_callback: Optional callback invoked once per key
191
+ after that key's put completes (not once per batch).
192
+ """
193
+ if not self.use_hot:
194
+ return
195
+
196
+ # TODO(Jiayi): optimize this with batching
197
+ for key, memory_obj in zip(keys, memory_objs, strict=False):
198
+ self.submit_put_task(
199
+ key, memory_obj, on_complete_callback=on_complete_callback
200
+ )
201
+
202
+ def get_blocking(
203
+ self,
204
+ key: CacheEngineKey,
205
+ ) -> Optional[MemoryObj]:
206
+ with self.cpu_lock:
207
+ if key not in self.hot_cache:
208
+ return None
209
+ memory_obj = self.hot_cache[key]
210
+ # ref count up for caller to avoid situation where the memory_obj
211
+ # is evicted from the local cpu backend before the caller calls
212
+ # ref count up themselves
213
+ memory_obj.ref_count_up()
214
+ return memory_obj
215
+
216
+ async def batched_get_non_blocking(
217
+ self,
218
+ lookup_id: str,
219
+ keys: list[CacheEngineKey],
220
+ transfer_spec: Any = None,
221
+ ) -> list[MemoryObj]:
222
+ mem_objs = []
223
+ with self.cpu_lock:
224
+ for key in keys:
225
+ mem_obj = self.hot_cache[key]
226
+ mem_obj.ref_count_up()
227
+ mem_objs.append(mem_obj)
228
+ return mem_objs
229
+
230
+ async def batched_async_contains(
231
+ self,
232
+ lookup_id: str,
233
+ keys: List[CacheEngineKey],
234
+ pin: bool = False,
235
+ ) -> int:
236
+ # NOTE(Jiayi): Only prefix chunks are counted.
237
+ num_hit_chunks = 0
238
+ with self.cpu_lock:
239
+ for key in keys:
240
+ if key not in self.hot_cache:
241
+ return num_hit_chunks
242
+ if pin:
243
+ self.hot_cache[key].pin()
244
+ # vllm lookup sets pin to True
245
+ self.keys_in_request.append(key)
246
+ num_hit_chunks += 1
247
+ return num_hit_chunks
248
+
249
+ def pin(self, key: CacheEngineKey) -> bool:
250
+ with self.cpu_lock:
251
+ if key not in self.hot_cache:
252
+ return False
253
+ memory_obj = self.hot_cache[key]
254
+ memory_obj.pin()
255
+ return True
256
+
257
+ def unpin(self, key: CacheEngineKey) -> bool:
258
+ with self.cpu_lock:
259
+ if key not in self.hot_cache:
260
+ return False
261
+ memory_obj = self.hot_cache[key]
262
+ memory_obj.unpin()
263
+ return True
264
+
265
+ def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
266
+ if force:
267
+ self.cpu_lock.acquire()
268
+ if key not in self.hot_cache:
269
+ if force:
270
+ self.cpu_lock.release()
271
+ return False
272
+
273
+ memory_obj = self.hot_cache.pop(key)
274
+ memory_obj.ref_count_down()
275
+
276
+ if force:
277
+ self.cache_policy.update_on_force_evict(key)
278
+ self.cpu_lock.release()
279
+
280
+ if self.batched_msg_sender is not None:
281
+ self.batched_msg_sender.add_kv_op(
282
+ op_type=OpType.EVICT,
283
+ key=key.chunk_hash,
284
+ )
285
+ # NOTE (Jiayi): This `return True` might not accurately reflect
286
+ # whether the key is removed from the actual memory because
287
+ # other backends might still (temporarily) hold the memory object.
288
+ return True
289
+
290
+ def _calculate_effective_cpu_size(
291
+ self,
292
+ configured_cpu_size: float,
293
+ config: LMCacheEngineConfig,
294
+ metadata: Optional[LMCacheMetadata] = None,
295
+ ) -> float:
296
+ """
297
+ Calculate the effective CPU memory size based on system available memory
298
+ and reserve memory configuration.
299
+
300
+ Args:
301
+ configured_cpu_size: The configured CPU memory size in GB
302
+ config: The LMCache engine configuration
303
+ metadata: Optional metadata for first rank handling
304
+
305
+ Returns:
306
+ The effective CPU memory size in GB
307
+ """
308
+
309
+ save_only_first_rank = (
310
+ metadata is not None
311
+ and config.get_extra_config_value("save_only_first_rank", metadata.use_mla)
312
+ and metadata.use_mla
313
+ )
314
+ if not save_only_first_rank:
315
+ # Do not adjust cpu_size if save_only_first_rank is False for now
316
+ return configured_cpu_size
317
+
318
+ # Get the system available memory and calculate effective cpu_size
319
+ system_available_memory_gb = SystemMemoryDetector.get_available_memory_gb()
320
+ # Get reserve memory size from config
321
+ reserve_cpu_size = config.reserve_local_cpu_size
322
+
323
+ # TODO(baoloongmao): For disable save_only_first_rank case,
324
+ # we need to avoid multi-rank race condition in future.
325
+ # But for enable save_only_first_rank case,
326
+ # we can handle reserve memory simply since non-first ranks
327
+ # do not allocate memory.
328
+ # Effective memory: min(configured_size, available_memory - reserve_size)
329
+ if system_available_memory_gb > 0:
330
+ max_usable_memory = max(0, system_available_memory_gb - reserve_cpu_size)
331
+ effective_cpu_size = min(configured_cpu_size, max_usable_memory)
332
+ logger.info(
333
+ f"Adjusted CPU memory size from {configured_cpu_size:.2f} GB "
334
+ f"to {effective_cpu_size:.2f} GB "
335
+ f"(system available: {system_available_memory_gb:.2f} GB, "
336
+ f"reserve: {reserve_cpu_size:.2f} GB)"
337
+ )
338
+ assert effective_cpu_size > 0
339
+ return effective_cpu_size
340
+ else:
341
+ logger.warning(
342
+ "Could not determine system available memory, using configured cpu_size"
343
+ )
344
+ return configured_cpu_size
345
+
346
+ def initialize_allocator(
347
+ self,
348
+ config: LMCacheEngineConfig,
349
+ metadata: Optional[LMCacheMetadata] = None,
350
+ ) -> MemoryAllocatorInterface:
351
+ cpu_size = config.max_local_cpu_size
352
+
353
+ if metadata is not None:
354
+ # save_only_first_rank only works when use mla
355
+ save_only_first_rank = (
356
+ config.get_extra_config_value("save_only_first_rank", metadata.use_mla)
357
+ and metadata.use_mla
358
+ )
359
+
360
+ if save_only_first_rank and metadata.is_first_rank():
361
+ # Only the first rank will save the cache,
362
+ # so we need to set it larger than other ranks
363
+ cpu_size = config.get_extra_config_value(
364
+ "first_rank_max_local_cpu_size", cpu_size
365
+ )
366
+
367
+ # Detect the numa mapping
368
+ numa_mapping = NUMADetector.get_numa_mapping(config)
369
+ logger.info(f"NUMA mapping {numa_mapping}")
370
+
371
+ # Calculate effective CPU memory size
372
+ cpu_size = self._calculate_effective_cpu_size(cpu_size, config, metadata)
373
+ cpu_size_bytes = int(cpu_size * 1024**3)
374
+
375
+ allocator_align_bytes = self._resolve_local_cpu_allocator_alignment(config)
376
+ if allocator_align_bytes is not None:
377
+ logger.info(
378
+ "LocalCPUBackend: using pinned allocation alignment=%d bytes",
379
+ allocator_align_bytes,
380
+ )
381
+
382
+ if config.enable_p2p:
383
+ # TODO(baoloongmao): Add lazy memory allocator support for P2P mode
384
+ # For now, keep the original P2P implementation
385
+ assert metadata is not None
386
+ shapes = metadata.get_shapes()
387
+ dtypes = metadata.get_dtypes()
388
+
389
+ paged_mem_allocator = PagedCpuGpuMemoryAllocator()
390
+ chunk_size_bytes = get_size_bytes(shapes, dtypes)
391
+ origin_cpu_size_bytes = cpu_size_bytes
392
+ align_cpu_size_bytes = (
393
+ origin_cpu_size_bytes // chunk_size_bytes * chunk_size_bytes
394
+ )
395
+ logger.info(
396
+ f"Auto align cpu size bytes, origin: {origin_cpu_size_bytes}, "
397
+ f"aligned: {align_cpu_size_bytes}, chunk size: {chunk_size_bytes}"
398
+ )
399
+ paged_mem_allocator.init_cpu_memory_allocator(
400
+ align_cpu_size_bytes,
401
+ shapes=shapes,
402
+ dtypes=dtypes,
403
+ fmt=MemoryFormat.KV_2LTD, # TODO: remove this hardcode
404
+ numa_mapping=numa_mapping,
405
+ )
406
+ return paged_mem_allocator
407
+ else:
408
+ # Check if lazy memory allocator should be enabled
409
+ use_lazy = (
410
+ config.enable_lazy_memory_allocator
411
+ and cpu_size > config.lazy_memory_safe_size
412
+ )
413
+ if use_lazy:
414
+ logger.warning(
415
+ "LazyMixedMemoryAllocator is temporarily unavailable; "
416
+ "falling back to MixedMemoryAllocator with full allocation. "
417
+ "Disable enable_lazy_memory_allocator or reduce "
418
+ "max_local_cpu_size to avoid large pinned allocations."
419
+ )
420
+ elif config.enable_lazy_memory_allocator:
421
+ logger.info(
422
+ f"LazyMixedMemoryAllocator is disabled because "
423
+ f"cpu_size ({cpu_size:.2f} GB) does not exceed "
424
+ f"lazy_memory_safe_size "
425
+ f"({config.lazy_memory_safe_size:.2f} GB). "
426
+ f"Using MixedMemoryAllocator instead."
427
+ )
428
+ if allocator_align_bytes is not None:
429
+ return MixedMemoryAllocator(
430
+ cpu_size_bytes,
431
+ numa_mapping=numa_mapping,
432
+ align_bytes=allocator_align_bytes,
433
+ )
434
+ return MixedMemoryAllocator(
435
+ cpu_size_bytes,
436
+ numa_mapping=numa_mapping,
437
+ config=config,
438
+ )
439
+
440
+ @staticmethod
441
+ def _is_power_of_two(value: int) -> bool:
442
+ return value > 0 and (value & (value - 1)) == 0
443
+
444
+ def _resolve_local_cpu_allocator_alignment(
445
+ self, config: LMCacheEngineConfig
446
+ ) -> Optional[int]:
447
+ """
448
+ Determine pinned-memory alignment for LocalCPUBackend allocator.
449
+
450
+ Precedence:
451
+ 1) explicit override: extra_config["local_cpu.pinned_align_bytes"]
452
+ 2) rust raw block auto mode:
453
+ - rust_raw_block.device_path is set
454
+ - rust_raw_block.use_odirect is true
455
+ - rust_raw_block.align_local_cpu_allocator is true (default)
456
+ -> use rust_raw_block.block_align
457
+ 3) None (use allocator default)
458
+ """
459
+ extra = config.extra_config or {}
460
+
461
+ explicit_align = extra.get("local_cpu.pinned_align_bytes")
462
+ if explicit_align is not None:
463
+ align = int(explicit_align)
464
+ if not self._is_power_of_two(align):
465
+ raise ValueError(
466
+ "extra_config['local_cpu.pinned_align_bytes'] must be "
467
+ "a positive power of two"
468
+ )
469
+ return align
470
+
471
+ rust_device_path = extra.get("rust_raw_block.device_path")
472
+ rust_use_odirect = bool(extra.get("rust_raw_block.use_odirect", False))
473
+ rust_auto_align = bool(
474
+ extra.get("rust_raw_block.align_local_cpu_allocator", True)
475
+ )
476
+
477
+ if not rust_device_path or not rust_use_odirect or not rust_auto_align:
478
+ return None
479
+
480
+ rust_block_align = int(extra.get("rust_raw_block.block_align", 4096))
481
+ if not self._is_power_of_two(rust_block_align):
482
+ raise ValueError(
483
+ "extra_config['rust_raw_block.block_align'] must be a positive "
484
+ "power of two when O_DIRECT alignment is enabled"
485
+ )
486
+ return rust_block_align
487
+
488
+ @_lmcache_nvtx_annotate
489
+ def allocate(
490
+ self,
491
+ shapes: Union[torch.Size, list[torch.Size]],
492
+ dtypes: Union[torch.dtype, list[torch.dtype]],
493
+ fmt: Optional[MemoryFormat] = None,
494
+ eviction: bool = True,
495
+ busy_loop: bool = True,
496
+ ) -> Optional[MemoryObj]:
497
+ """
498
+ Allocate a memory object of shape and dtype
499
+ evict if necessary. Storage manager should always call
500
+ local_cpu_backend.allocate() to get memory objects
501
+ regardless of whether local_cpu is True or False
502
+
503
+ busy_loop should only be used for retrieve
504
+ the reasoning is that:
505
+
506
+ 1. synchronous case
507
+ - many stores happen concurrently (if they busy_loop, deadlock happens)
508
+ - one retrieve at a time (okay to busy loop because stores will clear)
509
+
510
+ 2. asynchronous case
511
+ - many stores happen concurrently (if they busy_loop, deadlock happens)
512
+ - many retrieves happen concurrently
513
+ (we use the async serializer to handle this)
514
+ """
515
+ logger.debug(
516
+ f"Allocating memory in local cpu backend with busy loop: {busy_loop}"
517
+ )
518
+ if fmt is None:
519
+ if self.layerwise:
520
+ if self.enable_blending:
521
+ fmt = MemoryFormat.KV_2TD
522
+ else:
523
+ fmt = MemoryFormat.KV_T2D
524
+ else:
525
+ fmt = MemoryFormat.KV_2LTD
526
+
527
+ memory_obj = self.memory_allocator.allocate(shapes, dtypes, fmt)
528
+ if memory_obj is not None or not eviction:
529
+ return memory_obj
530
+
531
+ evict_keys_count = 0
532
+ num_attempts = 0
533
+ while True:
534
+ # whether or not this request needs to wait or other requests
535
+ wait_other_requests = True
536
+ if self.use_hot:
537
+ # TODO(Jiayi): optimize `num_candidates` with estimation.
538
+ # Accurate estimation is hard due to fragmentation
539
+ num_candidates = 1
540
+ evict_keys = None
541
+ with self.cpu_lock:
542
+ evict_keys = self.cache_policy.get_evict_candidates(
543
+ self.hot_cache, num_candidates=num_candidates
544
+ )
545
+ if evict_keys:
546
+ # we can continue trying to evict from the hot_cache
547
+ # and don't need to wait for other requests yet
548
+ wait_other_requests = False
549
+ logger.debug(
550
+ f"Evicting {len(evict_keys)} chunks from cpu memory"
551
+ )
552
+ # remove
553
+ self.batched_remove(evict_keys, force=False)
554
+ evict_keys_count += len(evict_keys)
555
+ else:
556
+ self.stats_monitor.update_local_cpu_evict_failed_count(
557
+ num_candidates
558
+ )
559
+
560
+ if wait_other_requests:
561
+ if not busy_loop:
562
+ logger.debug(
563
+ "Not busy looping because we are not immediately able to evict"
564
+ )
565
+ break
566
+
567
+ # TODO: make time_to_wait a config
568
+ time_to_wait = 0.1
569
+ logger.warning(
570
+ "No eviction candidates found in local cpu backend. "
571
+ "Local cpu memory is under pressure. "
572
+ f"Waiting for {time_to_wait} seconds before retrying."
573
+ )
574
+ # self.memory_allocator.memcheck()
575
+ # do not hold the lock during sleep
576
+ time.sleep(time_to_wait)
577
+
578
+ memory_obj = self.memory_allocator.allocate(shapes, dtypes, fmt)
579
+ if memory_obj is not None:
580
+ break
581
+
582
+ num_attempts += 1
583
+ logger.debug(
584
+ f"Unable to allocate memory object after {num_attempts}"
585
+ " attempts of local cpu backend allocate()"
586
+ )
587
+
588
+ self.stats_monitor.update_local_cpu_evict_metrics(evict_keys_count)
589
+ return memory_obj
590
+
591
+ @_lmcache_nvtx_annotate
592
+ def batched_allocate(
593
+ self,
594
+ shapes: Union[torch.Size, list[torch.Size]],
595
+ dtypes: Union[torch.dtype, list[torch.dtype]],
596
+ batch_size: int,
597
+ fmt: Optional[MemoryFormat] = None,
598
+ eviction: bool = True,
599
+ busy_loop: bool = True,
600
+ ) -> Optional[List[MemoryObj]]:
601
+ """
602
+ Batched allocate `batch_size` memory objects of shape and dtype
603
+ evict if necessary. Storage manager should always call
604
+ local_cpu_backend.allocate() to get memory objects
605
+ regardless of whether local_cpu is True or False
606
+
607
+ busy_loop should only be used for retrieve
608
+ the reasoning is that:
609
+
610
+ 1. synchronous case
611
+ - many stores happen concurrently (if they busy_loop, deadlock happens)
612
+ - one retrieve at a time (okay to busy loop because stores will clear)
613
+
614
+ 2. asynchronous case
615
+ - many stores happen concurrently (if they busy_loop, deadlock happens)
616
+ - many retrieves happen concurrently
617
+ (we use the async serializer to handle this)
618
+ """
619
+ logger.debug(
620
+ f"Batched allocating memory in local cpu backend"
621
+ f" with busy loop: {busy_loop}"
622
+ )
623
+ if fmt is None:
624
+ if self.layerwise:
625
+ if self.enable_blending:
626
+ fmt = MemoryFormat.KV_2TD
627
+ else:
628
+ fmt = MemoryFormat.KV_T2D
629
+ else:
630
+ fmt = MemoryFormat.KV_2LTD
631
+
632
+ memory_objs = self.memory_allocator.batched_allocate(
633
+ shapes, dtypes, batch_size, fmt
634
+ )
635
+
636
+ if memory_objs is not None or not eviction:
637
+ return memory_objs
638
+
639
+ assert isinstance(self.memory_allocator, MixedMemoryAllocator)
640
+
641
+ evict_keys_count = 0
642
+ num_attempts = 0
643
+ while True:
644
+ wait_other_requests = True
645
+ if self.use_hot:
646
+ # TODO(Jiayi): optimize `num_candidates` with estimation.
647
+ # Accurate estimation is hard due to fragmentation
648
+ num_candidates = 1
649
+ evict_keys = None
650
+ with self.cpu_lock:
651
+ evict_keys = self.cache_policy.get_evict_candidates(
652
+ self.hot_cache, num_candidates=num_candidates
653
+ )
654
+
655
+ # HACK: We assume batch_size=num_layers here.
656
+ # FIXME: We also assume if the one layer's ref_count > 1 or pinned,
657
+ # then the other layers are also ref_count > 1 or
658
+ # pinned in the cpu memory. This might not be true.
659
+ if evict_keys:
660
+ evict_keys_count += len(evict_keys)
661
+ wait_other_requests = False
662
+ for evict_key in evict_keys:
663
+ evict_key_all_layer = evict_key.split_layers(batch_size)
664
+
665
+ # TODO(Jiayi): batched allocate is not supported through
666
+ # `batched_remove`. Therefore, features like usage tracking
667
+ # is not supported.
668
+ old_mem_objs = []
669
+ for key in evict_key_all_layer:
670
+ old_mem_objs.append(self.hot_cache[key])
671
+ self.cache_policy.update_on_force_evict(key)
672
+ self.hot_cache.pop(key, None)
673
+
674
+ self.memory_allocator.batched_free(old_mem_objs)
675
+
676
+ logger.debug(
677
+ f"Evicting {len(old_mem_objs)} chunks from cpu memory"
678
+ )
679
+ else:
680
+ self.stats_monitor.update_local_cpu_evict_failed_count(
681
+ num_candidates
682
+ )
683
+
684
+ if wait_other_requests:
685
+ if not busy_loop:
686
+ logger.debug(
687
+ "Not busy looping because we are not immediately able to evict"
688
+ )
689
+ break
690
+
691
+ # TODO: make time_to_wait a config
692
+ time_to_wait = 0.1
693
+ logger.warning(
694
+ "No eviction candidates found in local cpu backend. "
695
+ "Local cpu memory is under pressure. "
696
+ f"Waiting for {time_to_wait} seconds before retrying."
697
+ )
698
+ # self.memory_allocator.memcheck()
699
+ # do not hold the lock during sleep
700
+ time.sleep(time_to_wait)
701
+
702
+ memory_objs = self.memory_allocator.batched_allocate(
703
+ shapes, dtypes, batch_size, fmt
704
+ )
705
+ if memory_objs:
706
+ break
707
+
708
+ num_attempts += 1
709
+ logger.debug(
710
+ f"Unable to allocate memory object after {num_attempts}"
711
+ " attempts of local cpu backend batched_allocate()"
712
+ )
713
+ self.stats_monitor.update_local_cpu_evict_metrics(evict_keys_count)
714
+ return memory_objs
715
+
716
+ def get_full_chunk_size_bytes(self) -> int:
717
+ logger.info("Calculating the size of a single LMCache chunk")
718
+ assert self.metadata is not None, (
719
+ "metadata required for chunk budget calculation"
720
+ )
721
+
722
+ chunk_tokens = self.config.chunk_size
723
+ # already accounted for parallelism
724
+ kv_shape = (
725
+ self.metadata.kv_shape
726
+ ) # [num_layers, kv_size, chunk_size, num_heads, head_size]
727
+ num_layers = kv_shape[0]
728
+ kv_size = kv_shape[1] # 1 for MLA, 2 for regular
729
+ # per gpu
730
+ num_heads = kv_shape[3]
731
+ head_size = kv_shape[4]
732
+ hidden_dim = num_heads * head_size
733
+ dtype_size = self.metadata.kv_dtype.itemsize
734
+
735
+ if self.layerwise:
736
+ # layerwise: [chunk_tokens, kv_size, hidden_dim]
737
+ chunk_bytes = chunk_tokens * kv_size * hidden_dim * dtype_size
738
+ else:
739
+ # full: [kv_size, num_layers, chunk_tokens, hidden_dim]
740
+ chunk_bytes = kv_size * num_layers * chunk_tokens * hidden_dim * dtype_size
741
+ logger.debug(
742
+ f"Stats received: num_layers={num_layers}, kv_size={kv_size}, "
743
+ f"chunk_tokens={chunk_tokens}, head_dim={head_size}, "
744
+ f"dtype_size={dtype_size}, "
745
+ f"hidden_dim={hidden_dim}"
746
+ )
747
+ logger.debug(f"Calculated bytes per chunk per rank: {chunk_bytes}")
748
+ return chunk_bytes
749
+
750
+ def calculate_chunk_budget(self) -> int:
751
+ """
752
+ Calculate the maximum number of chunks that can be allocated concurrently
753
+ without causing memory deadlocks in the async loading system.
754
+
755
+ Returns:
756
+ int: The estimated chunk budget for concurrent allocations
757
+ """
758
+ total_memory = int(self.config.max_local_cpu_size * 1024**3)
759
+ chunk_bytes = self.get_full_chunk_size_bytes()
760
+ # add alignment overhead
761
+ # (MixedMemoryAllocator uses TensorMemoryAllocator with 4KB alignment)
762
+ assert hasattr(self.memory_allocator, "align_bytes")
763
+ alignment = self.memory_allocator.align_bytes
764
+ aligned_chunk_bytes = ((chunk_bytes + alignment - 1) // alignment) * alignment
765
+
766
+ # calculate budget with safety margin
767
+ max_chunks = total_memory // aligned_chunk_bytes
768
+
769
+ return max_chunks
770
+
771
+ def get_keys(self) -> List[CacheEngineKey]:
772
+ """
773
+ array ordering of keys from LRU to MRU
774
+ """
775
+ with self.cpu_lock:
776
+ return list(self.hot_cache.keys())
777
+
778
+ def clear(self) -> int:
779
+ """
780
+ counts the number of memory objects removed
781
+ """
782
+ if not self.use_hot:
783
+ return 0
784
+ clear_keys = []
785
+ num_cleared_tokens = 0
786
+ with self.cpu_lock:
787
+ for key in self.hot_cache:
788
+ memory_obj = self.hot_cache[key]
789
+ if not memory_obj.can_evict:
790
+ continue
791
+ clear_keys.append(key)
792
+ num_cleared_tokens += memory_obj.get_num_tokens()
793
+
794
+ # TODO(Jiayi): might not be accurate if we don't calculate
795
+ # `num_cleared_token` and remove the keys in an atomic way.
796
+ self.batched_remove(clear_keys)
797
+
798
+ return num_cleared_tokens
799
+
800
+ def get_allocator_backend(self):
801
+ return self
802
+
803
+ def get_memory_allocator(self):
804
+ return self.memory_allocator
805
+
806
+ def close(self) -> None:
807
+ if self.batched_msg_sender is not None:
808
+ self.batched_msg_sender.close()
809
+ self.memory_allocator.close()
810
+ self.clear()