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,656 @@
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
5
+ import asyncio
6
+ import os
7
+ import threading
8
+ import time
9
+
10
+ # Third Party
11
+ import torch
12
+
13
+ # First Party
14
+ from lmcache.logging import init_logger
15
+ from lmcache.observability import LMCStatsMonitor
16
+ from lmcache.utils import CacheEngineKey, DiskCacheMetadata, _lmcache_nvtx_annotate
17
+ from lmcache.v1.cache_controller.message import OpType
18
+ from lmcache.v1.config import LMCacheEngineConfig
19
+ from lmcache.v1.memory_management import MemoryFormat, MemoryObj
20
+ from lmcache.v1.metadata import LMCacheMetadata
21
+ from lmcache.v1.storage_backend.abstract_backend import StorageBackendInterface
22
+ from lmcache.v1.storage_backend.batched_message_sender import BatchedMessageSender
23
+ from lmcache.v1.storage_backend.cache_policy import get_cache_policy
24
+ from lmcache.v1.storage_backend.job_executor.pq_executor import (
25
+ AsyncPQThreadPoolExecutor,
26
+ )
27
+ from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
28
+ from lmcache.v1.storage_backend.path_sharder import PathSharder
29
+
30
+ if TYPE_CHECKING:
31
+ # First Party
32
+ from lmcache.v1.cache_controller.worker import LMCacheWorker
33
+
34
+ logger = init_logger(__name__)
35
+
36
+
37
+ # TODO(Jiayi): handle cases where cache is repetitvely prefetched.
38
+ class LocalDiskWorker:
39
+ def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
40
+ self.put_lock = threading.Lock()
41
+ self.put_tasks: List[CacheEngineKey] = []
42
+
43
+ self.prefetch_lock = threading.Lock()
44
+ self.prefetch_tasks: dict[CacheEngineKey, Future] = {}
45
+
46
+ # TODO(Jiayi): make executor and its parameters configurable
47
+ self.executor = AsyncPQThreadPoolExecutor(loop, max_workers=4)
48
+ self.loop = loop
49
+ self._closed = False
50
+
51
+ async def submit_task(
52
+ self,
53
+ task_type: str,
54
+ task: Callable,
55
+ *args,
56
+ **kwargs,
57
+ ) -> Any:
58
+ if task_type == "prefetch":
59
+ priority = 0
60
+ # self.insert_prefetch_task(kwargs["key"], None)
61
+ elif task_type == "delete":
62
+ priority = 1
63
+ elif task_type == "put":
64
+ priority = 2
65
+ else:
66
+ raise ValueError(f"Unknown task type: {task_type}")
67
+
68
+ return await self.executor.submit_job(
69
+ task,
70
+ *args,
71
+ priority=priority,
72
+ **kwargs,
73
+ )
74
+
75
+ def remove_put_task(self, key: CacheEngineKey):
76
+ with self.put_lock:
77
+ if key in self.put_tasks:
78
+ self.put_tasks.remove(key)
79
+ else:
80
+ logger.warning(f"Key {key} not found in put tasks.")
81
+
82
+ def insert_put_task(self, key: CacheEngineKey):
83
+ with self.put_lock:
84
+ self.put_tasks.append(key)
85
+
86
+ def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
87
+ with self.put_lock:
88
+ return key in self.put_tasks
89
+
90
+ def close(self):
91
+ # Gracefully shut down the executor
92
+ if self._closed:
93
+ return
94
+ self._closed = True
95
+ self.executor.shutdown(wait=True)
96
+
97
+
98
+ class LocalDiskBackend(StorageBackendInterface):
99
+ def __init__(
100
+ self,
101
+ config: LMCacheEngineConfig,
102
+ loop: asyncio.AbstractEventLoop,
103
+ local_cpu_backend: LocalCPUBackend,
104
+ dst_device: str = "cuda",
105
+ lmcache_worker: Optional["LMCacheWorker"] = None,
106
+ metadata: Optional[LMCacheMetadata] = None,
107
+ ):
108
+ if torch.cuda.is_available():
109
+ super().__init__(dst_device)
110
+ else:
111
+ super().__init__("cpu")
112
+
113
+ self.cache_policy = get_cache_policy(config.cache_policy)
114
+ self.dict = self.cache_policy.init_mutable_mapping()
115
+
116
+ self.dst_device = dst_device
117
+
118
+ self.local_cpu_backend = local_cpu_backend
119
+
120
+ self.disk_lock = threading.Lock()
121
+
122
+ assert config.local_disk is not None
123
+
124
+ sharder = PathSharder(
125
+ raw_csv=config.local_disk,
126
+ strategy=config.local_disk_path_sharding,
127
+ dst_device=dst_device,
128
+ create_dirs=True,
129
+ )
130
+ self.path: str = sharder.selected
131
+
132
+ logger.info(
133
+ "Local disk cache path: %s (device %s, %d path(s) configured)",
134
+ self.path,
135
+ dst_device,
136
+ len(sharder.all_paths),
137
+ )
138
+
139
+ self.loop = loop
140
+
141
+ self.use_local_cpu = config.local_cpu
142
+
143
+ # Block size (for file system I/O)
144
+ stat = os.statvfs(self.path)
145
+ self.os_disk_bs = stat.f_bsize
146
+ self.use_odirect = False
147
+
148
+ if config.extra_config is not None:
149
+ self.use_odirect = config.extra_config.get("use_odirect", False)
150
+ logger.info("Using O_DIRECT for disk I/O: %s", self.use_odirect)
151
+
152
+ self.disk_worker = LocalDiskWorker(loop)
153
+
154
+ # TODO(Jiayi): We need a disk space allocator to avoid fragmentation
155
+ # and hide the following details away from the backend.
156
+ self.max_cache_size = int(config.max_local_disk_size * 1024**3)
157
+ self.current_cache_size = 0.0
158
+
159
+ # to help maintain suffix -> prefix order in the dict
160
+ # assumption: only one request is looked up at a time
161
+ # (only one worker per cache engine)
162
+ self.keys_in_request: List[CacheEngineKey] = []
163
+
164
+ self.lmcache_worker = lmcache_worker
165
+ self.instance_id = config.lmcache_instance_id
166
+ self.stats_monitor = LMCStatsMonitor.GetOrCreate()
167
+ self.usage = 0
168
+
169
+ # Batched message sender for controller communication
170
+ self.batched_msg_sender: Optional[BatchedMessageSender] = None
171
+
172
+ # Initialize batched message sender
173
+ if lmcache_worker and metadata is not None:
174
+ self.batched_msg_sender = BatchedMessageSender(
175
+ metadata=metadata,
176
+ config=config,
177
+ location=str(self),
178
+ lmcache_worker=lmcache_worker,
179
+ )
180
+ else:
181
+ logger.warning("Controller message sender is not initialized")
182
+
183
+ def __str__(self) -> str:
184
+ return "LocalDiskBackend"
185
+
186
+ def _key_to_path(
187
+ self,
188
+ key: CacheEngineKey,
189
+ ) -> str:
190
+ return os.path.join(self.path, key.to_string().replace("/", "-") + ".pt")
191
+
192
+ def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
193
+ with self.disk_lock:
194
+ if key not in self.dict:
195
+ return False
196
+ if pin:
197
+ self.dict[key].pin()
198
+ # vllm lookup sets pin to True
199
+ self.keys_in_request.append(key)
200
+ return True
201
+
202
+ def touch_cache(self):
203
+ # flip the order of the keys in the request
204
+ with self.disk_lock:
205
+ for key in reversed(self.keys_in_request):
206
+ self.cache_policy.update_on_hit(key, self.dict)
207
+ self.keys_in_request = []
208
+
209
+ def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
210
+ return self.disk_worker.exists_in_put_tasks(key)
211
+
212
+ def pin(
213
+ self,
214
+ key: CacheEngineKey,
215
+ ) -> bool:
216
+ with self.disk_lock:
217
+ if key in self.dict:
218
+ self.dict[key].pin()
219
+ return True
220
+ else:
221
+ return False
222
+
223
+ def unpin(
224
+ self,
225
+ key: CacheEngineKey,
226
+ ) -> bool:
227
+ with self.disk_lock:
228
+ if key in self.dict:
229
+ self.dict[key].unpin()
230
+ return True
231
+ else:
232
+ return False
233
+
234
+ def remove(
235
+ self,
236
+ key: CacheEngineKey,
237
+ force: bool = True,
238
+ ) -> bool:
239
+ if force:
240
+ self.disk_lock.acquire()
241
+
242
+ if not (meta := self.dict.pop(key, None)):
243
+ if force:
244
+ self.disk_lock.release()
245
+ return False
246
+
247
+ path = meta.path
248
+ size = meta.size
249
+ self.usage -= size
250
+ self.stats_monitor.update_local_storage_usage(self.usage)
251
+
252
+ # NOTE: The following code will cause deadlock
253
+ # res = asyncio.run_coroutine_threadsafe(
254
+ # self.disk_worker.submit_task("delete", os.remove, path),
255
+ # self.loop,
256
+ # )
257
+ # res.result()
258
+
259
+ os.remove(path)
260
+
261
+ if force:
262
+ self.cache_policy.update_on_force_evict(key)
263
+ self.disk_lock.release()
264
+
265
+ # Push kv evict msg with batching
266
+ if self.batched_msg_sender is not None:
267
+ self.batched_msg_sender.add_kv_op(
268
+ op_type=OpType.EVICT,
269
+ key=key.chunk_hash,
270
+ )
271
+
272
+ return True
273
+
274
+ def insert_key(
275
+ self,
276
+ key: CacheEngineKey,
277
+ size: int,
278
+ shape: torch.Size,
279
+ dtype: torch.dtype,
280
+ fmt: MemoryFormat,
281
+ cached_positions: Optional[torch.Tensor] = None,
282
+ ) -> None:
283
+ path = self._key_to_path(key)
284
+
285
+ has_stored = False
286
+ with self.disk_lock:
287
+ if key in self.dict:
288
+ # Update cache recency
289
+ self.cache_policy.update_on_hit(key, self.dict)
290
+ has_stored = True
291
+ else:
292
+ self.dict[key] = DiskCacheMetadata(
293
+ path, size, shape, dtype, cached_positions, fmt, 0
294
+ )
295
+
296
+ # Push kv admit msg with batching
297
+ if self.batched_msg_sender is not None and not has_stored:
298
+ self.batched_msg_sender.add_kv_op(
299
+ op_type=OpType.ADMIT,
300
+ key=key.chunk_hash,
301
+ )
302
+
303
+ def submit_put_task(
304
+ self,
305
+ key: CacheEngineKey,
306
+ memory_obj: MemoryObj,
307
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
308
+ ):
309
+ """
310
+ Submit a single put task to store KV cache to disk asynchronously.
311
+
312
+ :param key: The cache key for this KV chunk.
313
+ :param memory_obj: The memory object containing the KV data.
314
+ :param on_complete_callback: Optional callback invoked once per key
315
+ after the disk write completes. Callback exceptions are caught
316
+ and logged.
317
+ """
318
+ assert memory_obj.tensor is not None
319
+
320
+ # skip repeated save
321
+ if self.exists_in_put_tasks(key):
322
+ logger.debug(f"Put task for {key} is already in progress.")
323
+ return None
324
+
325
+ self.disk_worker.insert_put_task(key)
326
+
327
+ # TODO(Jiayi): Fragmentation is not considered here.
328
+ required_size = memory_obj.get_physical_size()
329
+ all_evict_keys = []
330
+ evict_success = True
331
+ with self.disk_lock:
332
+ while self.current_cache_size + required_size > self.max_cache_size:
333
+ evict_keys = self.cache_policy.get_evict_candidates(
334
+ self.dict, num_candidates=1
335
+ )
336
+ if not evict_keys:
337
+ logger.warning(
338
+ "No eviction candidates found. Disk space under pressure."
339
+ )
340
+ evict_success = False
341
+ break
342
+
343
+ for evict_key in evict_keys:
344
+ self.current_cache_size -= self.dict[evict_key].size
345
+
346
+ self.batched_remove(evict_keys, force=False)
347
+
348
+ all_evict_keys.extend(evict_keys)
349
+ if evict_success:
350
+ self.current_cache_size += required_size
351
+ self.cache_policy.update_on_put(key)
352
+
353
+ if not evict_success:
354
+ return None
355
+
356
+ memory_obj.ref_count_up()
357
+
358
+ asyncio.run_coroutine_threadsafe(
359
+ self.disk_worker.submit_task(
360
+ "put",
361
+ self.async_save_bytes_to_disk,
362
+ key=key,
363
+ memory_obj=memory_obj,
364
+ on_complete_callback=on_complete_callback,
365
+ ),
366
+ self.loop,
367
+ )
368
+
369
+ # TODO(Jiayi): enable real batching
370
+ def batched_submit_put_task(
371
+ self,
372
+ keys: Sequence[CacheEngineKey],
373
+ memory_objs: List[MemoryObj],
374
+ transfer_spec: Any = None,
375
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
376
+ ) -> None:
377
+ """
378
+ Submit batched put tasks to store KV caches to disk asynchronously.
379
+
380
+ :param keys: The cache keys for the KV chunks.
381
+ :param memory_objs: The memory objects containing the KV data.
382
+ :param transfer_spec: Optional transfer specification (unused).
383
+ :param on_complete_callback: Optional callback invoked once per key
384
+ after that key's disk write completes (not once per batch).
385
+ Callback exceptions are caught and logged.
386
+ """
387
+ for key, memory_obj in zip(keys, memory_objs, strict=False):
388
+ self.submit_put_task(
389
+ key, memory_obj, on_complete_callback=on_complete_callback
390
+ )
391
+
392
+ def get_blocking(
393
+ self,
394
+ key: CacheEngineKey,
395
+ ) -> Optional[MemoryObj]:
396
+ """
397
+ Blocking get function.
398
+ """
399
+ self.disk_lock.acquire()
400
+ if key not in self.dict:
401
+ self.disk_lock.release()
402
+ return None
403
+
404
+ # Update cache recency
405
+ self.cache_policy.update_on_hit(key, self.dict)
406
+
407
+ disk_meta = self.dict[key]
408
+ path = disk_meta.path
409
+ dtype = disk_meta.dtype
410
+ shape = disk_meta.shape
411
+ fmt = disk_meta.fmt
412
+ assert dtype is not None
413
+ assert shape is not None
414
+
415
+ self.disk_lock.release()
416
+ memory_obj = self.load_bytes_from_disk(
417
+ key, path, dtype=dtype, shape=shape, fmt=fmt
418
+ )
419
+
420
+ return memory_obj
421
+
422
+ async def batched_get_non_blocking(
423
+ self,
424
+ lookup_id: str,
425
+ keys: list[CacheEngineKey],
426
+ transfer_spec: Any = None,
427
+ ) -> list[MemoryObj]:
428
+ mem_objs: list[MemoryObj] = []
429
+ paths: list[str] = []
430
+
431
+ logger.debug(f"lookup_id: {lookup_id}; Prefetching {len(keys)} keys from disk.")
432
+ for key in keys:
433
+ self.disk_lock.acquire()
434
+ assert key in self.dict, f"Key {key} not found in disk cache after pinning"
435
+
436
+ path = self.dict[key].path
437
+ dtype = self.dict[key].dtype
438
+ shape = self.dict[key].shape
439
+ fmt = self.dict[key].fmt
440
+
441
+ assert dtype is not None
442
+ assert shape is not None
443
+
444
+ # busy_loop=False prevents spinning on the event loop thread;
445
+ # if staging memory is exhausted the caller will get a logged
446
+ # error rather than a silent deadlock.
447
+ memory_obj = self.local_cpu_backend.allocate(
448
+ shape,
449
+ dtype,
450
+ fmt,
451
+ busy_loop=False,
452
+ )
453
+
454
+ if memory_obj is None:
455
+ logger.error(
456
+ "Memory allocation failed during async disk load for key %s. "
457
+ "CPU staging pool may be exhausted (unpin() not called after "
458
+ "a previous retrieve). Returning partial results.",
459
+ key,
460
+ )
461
+ return mem_objs
462
+
463
+ self.dict[key].pin()
464
+
465
+ # NOTE(Jiayi): Currently, we consider prefetch as cache hit.
466
+ # Update cache recency
467
+ self.cache_policy.update_on_hit(key, self.dict)
468
+
469
+ self.disk_lock.release()
470
+ logger.debug(f"Prefetching {key} from disk.")
471
+ memory_obj.pin()
472
+ mem_objs.append(memory_obj)
473
+ paths.append(path)
474
+
475
+ return await self.disk_worker.submit_task(
476
+ "prefetch",
477
+ self.batched_async_load_bytes_from_disk,
478
+ paths=paths,
479
+ keys=keys,
480
+ memory_objs=mem_objs,
481
+ )
482
+
483
+ async def batched_async_contains(
484
+ self,
485
+ lookup_id: str,
486
+ keys: list[CacheEngineKey],
487
+ pin: bool = False,
488
+ ) -> int:
489
+ num_hit_counts = 0
490
+ with self.disk_lock:
491
+ for key in keys:
492
+ if key not in self.dict:
493
+ return num_hit_counts
494
+ if pin:
495
+ self.dict[key].pin()
496
+ self.keys_in_request.append(key)
497
+ num_hit_counts += 1
498
+ return num_hit_counts
499
+
500
+ @_lmcache_nvtx_annotate
501
+ @torch.inference_mode()
502
+ def async_save_bytes_to_disk(
503
+ self,
504
+ key: CacheEngineKey,
505
+ memory_obj: MemoryObj,
506
+ on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
507
+ ) -> None:
508
+ """
509
+ Convert KV to bytes and async store bytes to disk.
510
+
511
+ :param on_complete_callback: Optional callback invoked after the disk
512
+ write completes for this key. Callback exceptions are caught and
513
+ logged.
514
+ """
515
+ kv_chunk = memory_obj.tensor
516
+ assert kv_chunk is not None
517
+ buffer = memory_obj.byte_array
518
+ path = self._key_to_path(key)
519
+
520
+ size = len(buffer)
521
+ self.usage += size
522
+ self.stats_monitor.update_local_storage_usage(self.usage)
523
+
524
+ # TODO(Jiayi): need to add ref count in disk memory object
525
+ self.write_file(buffer, path)
526
+
527
+ # ref count down here because there's a ref_count_up in
528
+ # `submit_put_task` above.
529
+ # Ref count down better be before `insert_key` for testing
530
+ # purposes (e.g., testing mem_leak).
531
+ # TODO(Jiayi): This could be problematic if the
532
+ # freed memory object is immediately reused.
533
+ size = memory_obj.get_physical_size()
534
+ shape = memory_obj.metadata.shape
535
+ dtype = memory_obj.metadata.dtype
536
+ fmt = memory_obj.metadata.fmt
537
+ cached_positions = memory_obj.metadata.cached_positions
538
+ memory_obj.ref_count_down()
539
+
540
+ self.insert_key(key, size, shape, dtype, fmt, cached_positions=cached_positions)
541
+
542
+ self.disk_worker.remove_put_task(key)
543
+
544
+ # Call the completion callback if provided
545
+ if on_complete_callback is not None:
546
+ try:
547
+ on_complete_callback(key)
548
+ except Exception as e:
549
+ logger.warning(f"on_complete_callback failed for key {key}: {e}")
550
+
551
+ def batched_async_load_bytes_from_disk(
552
+ self,
553
+ paths: list[str],
554
+ keys: list[CacheEngineKey],
555
+ memory_objs: list[MemoryObj],
556
+ write_back: bool = False,
557
+ ) -> list[MemoryObj]:
558
+ """
559
+ Async load bytearray from disk.
560
+ """
561
+
562
+ logger.debug("Executing `async_load_bytes` from disk.")
563
+ # TODO (Jiayi): handle the case where loading fails.
564
+ for path, key, mem_obj in zip(paths, keys, memory_objs, strict=False):
565
+ buffer = mem_obj.byte_array
566
+ self.read_file(key, buffer, path)
567
+
568
+ # TODO(Jiayi): Please recover the metadata in a more
569
+ # elegant way in the future.
570
+ cached_positions = self.dict[key].cached_positions
571
+ mem_obj.metadata.cached_positions = cached_positions
572
+
573
+ self.disk_lock.acquire()
574
+ self.dict[key].unpin()
575
+ self.disk_lock.release()
576
+
577
+ return memory_objs
578
+
579
+ def load_bytes_from_disk(
580
+ self,
581
+ key: CacheEngineKey,
582
+ path: str,
583
+ dtype: torch.dtype,
584
+ shape: torch.Size,
585
+ fmt: MemoryFormat,
586
+ ) -> Optional[MemoryObj]:
587
+ """
588
+ Load bytearray from disk.
589
+ """
590
+
591
+ memory_obj = self.local_cpu_backend.allocate(shape, dtype, fmt)
592
+ assert memory_obj is not None, "Memory allocation failed during disk load."
593
+
594
+ buffer = memory_obj.byte_array
595
+ self.read_file(key, buffer, path)
596
+
597
+ # TODO(Jiayi): Please recover the metadata in a more
598
+ # elegant way in the future.
599
+ cached_positions = self.dict[key].cached_positions
600
+ memory_obj.metadata.cached_positions = cached_positions
601
+
602
+ return memory_obj
603
+
604
+ def write_file(self, buffer, path):
605
+ start_time = time.time()
606
+ size = len(buffer)
607
+ if size % self.os_disk_bs != 0 or not self.use_odirect:
608
+ with open(path, "wb") as f:
609
+ f.write(buffer)
610
+ else:
611
+ fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_DIRECT, 0o644)
612
+ os.write(fd, buffer)
613
+ os.close(fd)
614
+ disk_write_time = time.time() - start_time
615
+ logger.debug(
616
+ f"Disk write size: {size} bytes, "
617
+ f"Bandwidth: {size / disk_write_time / 1e6:.2f} MB/s"
618
+ )
619
+
620
+ def read_file(self, key, buffer, path):
621
+ start_time = time.time()
622
+ size = len(buffer)
623
+ fblock_aligned = size % self.os_disk_bs == 0
624
+ if not fblock_aligned and self.use_odirect:
625
+ logger.warning(
626
+ "Cannot use O_DIRECT for this file, "
627
+ "size is not aligned to disk block size."
628
+ )
629
+
630
+ try:
631
+ if not fblock_aligned or not self.use_odirect:
632
+ with open(path, "rb") as f:
633
+ f.readinto(buffer)
634
+ else:
635
+ fd = os.open(path, os.O_RDONLY | os.O_DIRECT)
636
+ with os.fdopen(fd, "rb", buffering=0) as fdo:
637
+ fdo.readinto(buffer)
638
+ except FileNotFoundError:
639
+ logger.warning(f"File not found on disk: {path}")
640
+ if self.dict.get(key, None):
641
+ self.dict.pop(key)
642
+ return
643
+
644
+ disk_read_time = time.time() - start_time
645
+ logger.debug(
646
+ f"Disk read size: {size} bytes, "
647
+ f"Bandwidth: {size / disk_read_time / 1e6:.2f} MB/s"
648
+ )
649
+
650
+ def get_allocator_backend(self) -> LocalCPUBackend:
651
+ return self.local_cpu_backend
652
+
653
+ def close(self) -> None:
654
+ if self.batched_msg_sender is not None:
655
+ self.batched_msg_sender.close()
656
+ self.disk_worker.close()