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,290 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """L0 (GPU) KV cache metrics subscriber — OTel histograms for block lifecycle.
4
+
5
+ Subscribes to ``MP_VLLM_BLOCK_ALLOCATION`` and ``MP_VLLM_END_SESSION`` events
6
+ and maintains a shadow map of physical GPU block IDs to detect cache hits and
7
+ evictions.
8
+
9
+ State machine per block:
10
+
11
+ 1. Block first seen in allocation → ACTIVE (owned by req_id)
12
+ 2. Same block, same tokens, same req_id → ignore (decode continuation)
13
+ 3. Same block, same tokens, different req_id, block ACTIVE →
14
+ prefix sharing, just add req_id as co-owner
15
+ 4. Same block, same tokens, block RELEASED → true cache hit (reuse),
16
+ record access, transition to ACTIVE
17
+ 5. Same block, different tokens → eviction detected,
18
+ emit metrics, start fresh
19
+ 6. END_SESSION(req_id) → remove req_id from all blocks it owns.
20
+ If a block has no remaining owners → RELEASED.
21
+
22
+ Limitations:
23
+ - ``BlockAllocationRecord`` only reports **new** block IDs per request per
24
+ scheduler step, not all blocks a request uses. Blocks reused via prefix
25
+ cache are invisible after initial allocation. This means the subscriber
26
+ only tracks a subset of physical blocks and will undercount evictions
27
+ compared to vLLM's internal ``KVCacheMetricsCollector``.
28
+ - Eviction is detected at **reallocation** time, not at the exact moment
29
+ vLLM frees the block. Lifetime measurements include the gap between
30
+ eviction and reallocation.
31
+ """
32
+
33
+ # Future
34
+ from __future__ import annotations
35
+
36
+ # Standard
37
+ from collections import deque
38
+ from dataclasses import dataclass, field
39
+ from enum import Enum
40
+ import random
41
+ import time
42
+
43
+ # Third Party
44
+ from opentelemetry import metrics
45
+
46
+ # First Party
47
+ from lmcache.v1.mp_observability.event import Event, EventType
48
+ from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
49
+
50
+ # Maximum number of recent access timestamps kept per block (ring buffer).
51
+ _MAX_ACCESS_HISTORY = 4
52
+
53
+
54
+ class _BlockStatus(Enum):
55
+ ACTIVE = "active" # Owned by at least one live request.
56
+ RELEASED = "released" # All owning requests have ended.
57
+
58
+
59
+ @dataclass
60
+ class _L0BlockState:
61
+ """Per-block lifecycle state in the shadow map."""
62
+
63
+ instance_id: int
64
+ model_name: str
65
+ token_ids: list[int]
66
+ owners: set[str] # Set of req_ids currently using this block.
67
+ status: _BlockStatus
68
+ alloc_time: float
69
+ last_access_time: float
70
+ access_history: deque[float] = field(
71
+ default_factory=lambda: deque(maxlen=_MAX_ACCESS_HISTORY)
72
+ )
73
+
74
+
75
+ class L0LifecycleSubscriber(EventSubscriber):
76
+ """Tracks GPU (L0) KV cache block lifecycle via shadow monitoring.
77
+
78
+ Metrics (all histograms, in seconds):
79
+ - ``lmcache_mp.l0_block_lifetime_seconds``
80
+ - ``lmcache_mp.l0_block_idle_before_evict_seconds``
81
+ - ``lmcache_mp.l0_block_reuse_gap_seconds``
82
+
83
+ Parameters:
84
+ sample_rate: Fraction of blocks to track (0, 1.0]. Default 0.01
85
+ (1%) matches vLLM's default.
86
+ """
87
+
88
+ def __init__(self, sample_rate: float = 0.01) -> None:
89
+ assert 0 < sample_rate <= 1.0, (
90
+ f"sample_rate must be in (0, 1.0], got {sample_rate}"
91
+ )
92
+ self._sample_rate = sample_rate
93
+
94
+ # Shadow map: (instance_id, block_id) -> lifecycle state.
95
+ self._shadow: dict[tuple[int, int], _L0BlockState] = {}
96
+ # Set of (instance_id, block_id) we decided NOT to sample.
97
+ self._skipped: set[tuple[int, int]] = set()
98
+ # Reverse index: req_id -> set of (instance_id, block_id) owned.
99
+ self._req_blocks: dict[str, set[tuple[int, int]]] = {}
100
+
101
+ meter = metrics.get_meter("lmcache.l0")
102
+ self._lifetime_hist = meter.create_histogram(
103
+ "lmcache_mp.l0_block_lifetime_seconds",
104
+ description=(
105
+ "Histogram of GPU KV cache block lifetime from "
106
+ "allocation to eviction (seconds)."
107
+ ),
108
+ unit="s",
109
+ )
110
+ self._idle_hist = meter.create_histogram(
111
+ "lmcache_mp.l0_block_idle_before_evict_seconds",
112
+ description=(
113
+ "Histogram of idle time before GPU KV cache block eviction (seconds)."
114
+ ),
115
+ unit="s",
116
+ )
117
+ self._reuse_gap_hist = meter.create_histogram(
118
+ "lmcache_mp.l0_block_reuse_gap_seconds",
119
+ description=(
120
+ "Histogram of time gaps between consecutive GPU KV "
121
+ "cache block accesses (seconds)."
122
+ ),
123
+ unit="s",
124
+ )
125
+
126
+ # -- EventSubscriber interface -----------------------------------------
127
+
128
+ def get_subscriptions(self) -> dict[EventType, EventCallback]:
129
+ return {
130
+ EventType.MP_VLLM_BLOCK_ALLOCATION: self._on_block_allocation,
131
+ EventType.MP_VLLM_END_SESSION: self._on_end_session,
132
+ }
133
+
134
+ # -- Event handlers ----------------------------------------------------
135
+
136
+ def _on_block_allocation(self, event: Event) -> None:
137
+ """Process a batch of ``BlockAllocationRecord`` from vLLM."""
138
+ instance_id = event.metadata.get("instance_id", 0)
139
+ model_name = event.metadata.get("model_name", "")
140
+ records = event.metadata.get("records", [])
141
+ now = event.timestamp or time.time()
142
+
143
+ for record in records:
144
+ self._process_record(instance_id, model_name, record, now)
145
+
146
+ def _on_end_session(self, event: Event) -> None:
147
+ """Handle request completion — release blocks owned by this request."""
148
+ req_id = event.metadata.get("request_id", "")
149
+ if not req_id:
150
+ return
151
+
152
+ block_keys = self._req_blocks.pop(req_id, set())
153
+ for block_key in block_keys:
154
+ state = self._shadow.get(block_key)
155
+ if state is None:
156
+ continue
157
+ state.owners.discard(req_id)
158
+ if not state.owners:
159
+ state.status = _BlockStatus.RELEASED
160
+
161
+ # -- Record processing -------------------------------------------------
162
+
163
+ def _process_record(
164
+ self, instance_id: int, model_name: str, record: object, now: float
165
+ ) -> None:
166
+ """Process a single BlockAllocationRecord."""
167
+ req_id: str = record.req_id # type: ignore[attr-defined]
168
+ block_ids: list[int] = record.new_block_ids # type: ignore[attr-defined]
169
+ token_ids: list[int] = record.new_token_ids # type: ignore[attr-defined]
170
+
171
+ if not block_ids:
172
+ return
173
+
174
+ num_blocks = len(block_ids)
175
+ block_size = (
176
+ (len(token_ids) + num_blocks - 1) // num_blocks if num_blocks else 0
177
+ )
178
+
179
+ for i, block_id in enumerate(block_ids):
180
+ start = i * block_size
181
+ end = min(start + block_size, len(token_ids))
182
+ chunk_tokens = token_ids[start:end]
183
+ if not chunk_tokens:
184
+ continue
185
+ self._process_block(
186
+ instance_id, model_name, block_id, chunk_tokens, req_id, now
187
+ )
188
+
189
+ def _process_block(
190
+ self,
191
+ instance_id: int,
192
+ model_name: str,
193
+ block_id: int,
194
+ token_ids: list[int],
195
+ req_id: str,
196
+ now: float,
197
+ ) -> None:
198
+ """Update shadow map for a single physical block."""
199
+ block_key = (instance_id, block_id)
200
+ existing = self._shadow.get(block_key)
201
+
202
+ if existing is None:
203
+ # Block not in shadow map.
204
+ if block_key in self._skipped:
205
+ return
206
+
207
+ if not self._should_sample():
208
+ self._skipped.add(block_key)
209
+ return
210
+
211
+ # New allocation — start tracking.
212
+ self._shadow[block_key] = _L0BlockState(
213
+ instance_id=instance_id,
214
+ model_name=model_name,
215
+ token_ids=token_ids,
216
+ owners={req_id},
217
+ status=_BlockStatus.ACTIVE,
218
+ alloc_time=now,
219
+ last_access_time=now,
220
+ )
221
+ self._req_blocks.setdefault(req_id, set()).add(block_key)
222
+ return
223
+
224
+ # Block exists in shadow map.
225
+ if existing.token_ids == token_ids:
226
+ # Same content.
227
+ if req_id in existing.owners:
228
+ # Case 2: Same request reporting same block again (decode
229
+ # continuation or redundant report). Ignore.
230
+ return
231
+
232
+ if existing.status == _BlockStatus.ACTIVE:
233
+ # Case 3: Prefix sharing — another request is using this
234
+ # block while original request(s) still active. Not a reuse.
235
+ existing.owners.add(req_id)
236
+ self._req_blocks.setdefault(req_id, set()).add(block_key)
237
+ else:
238
+ # Case 4: Block was RELEASED, now reused with same content.
239
+ # This is a true cache hit.
240
+ existing.owners.add(req_id)
241
+ existing.status = _BlockStatus.ACTIVE
242
+ existing.last_access_time = now
243
+ existing.access_history.append(now)
244
+ self._req_blocks.setdefault(req_id, set()).add(block_key)
245
+ else:
246
+ # Case 5: Different content — eviction detected.
247
+ self._emit_eviction_metrics(existing, now)
248
+
249
+ # Clear old owners from reverse index.
250
+ for old_req in existing.owners:
251
+ block_set = self._req_blocks.get(old_req)
252
+ if block_set:
253
+ block_set.discard(block_key)
254
+
255
+ # Start fresh.
256
+ self._shadow[block_key] = _L0BlockState(
257
+ instance_id=instance_id,
258
+ model_name=model_name,
259
+ token_ids=token_ids,
260
+ owners={req_id},
261
+ status=_BlockStatus.ACTIVE,
262
+ alloc_time=now,
263
+ last_access_time=now,
264
+ )
265
+ self._req_blocks.setdefault(req_id, set()).add(block_key)
266
+
267
+ # -- Metrics emission --------------------------------------------------
268
+
269
+ def _emit_eviction_metrics(self, state: _L0BlockState, now: float) -> None:
270
+ """Record histogram observations for an evicted block."""
271
+ lifetime = now - state.alloc_time
272
+ idle_time = now - state.last_access_time
273
+ attrs = {
274
+ "instance_id": str(state.instance_id),
275
+ "model_name": state.model_name,
276
+ }
277
+
278
+ self._lifetime_hist.record(lifetime, attrs)
279
+ self._idle_hist.record(idle_time, attrs)
280
+
281
+ # Reuse gaps from access history.
282
+ history = list(state.access_history)
283
+ for i in range(1, len(history)):
284
+ gap = history[i] - history[i - 1]
285
+ self._reuse_gap_hist.record(gap, attrs)
286
+
287
+ # -- Sampling ----------------------------------------------------------
288
+
289
+ def _should_sample(self) -> bool:
290
+ return random.random() < self._sample_rate
@@ -0,0 +1,55 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """L1 metrics subscriber — OTel counters for L1Manager events."""
4
+
5
+ # Future
6
+ from __future__ import annotations
7
+
8
+ # Third Party
9
+ from opentelemetry import metrics
10
+
11
+ # First Party
12
+ from lmcache.v1.mp_observability.event import Event, EventType
13
+ from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
14
+
15
+
16
+ class L1MetricsSubscriber(EventSubscriber):
17
+ """Maintains OTel counters for L1Manager operations.
18
+
19
+ Metric parity with the old ``L1ManagerStatsLogger``:
20
+ - ``lmcache_mp.l1_read_keys`` — keys read from L1
21
+ - ``lmcache_mp.l1_write_keys`` — keys written to L1
22
+ - ``lmcache_mp.l1_evicted_keys`` — keys evicted from L1
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ meter = metrics.get_meter("lmcache.l1")
27
+ self._read_counter = meter.create_counter(
28
+ "lmcache_mp.l1_read_keys",
29
+ description="Total keys read from L1",
30
+ )
31
+ self._write_counter = meter.create_counter(
32
+ "lmcache_mp.l1_write_keys",
33
+ description="Total keys written to L1",
34
+ )
35
+ self._evicted_counter = meter.create_counter(
36
+ "lmcache_mp.l1_evicted_keys",
37
+ description="Total keys evicted from L1",
38
+ )
39
+
40
+ def get_subscriptions(self) -> dict[EventType, EventCallback]:
41
+ return {
42
+ EventType.L1_READ_FINISHED: self._on_read_finished,
43
+ EventType.L1_WRITE_FINISHED: self._on_write_finished,
44
+ EventType.L1_WRITE_FINISHED_AND_READ_RESERVED: self._on_write_finished,
45
+ EventType.L1_KEYS_EVICTED: self._on_evicted,
46
+ }
47
+
48
+ def _on_read_finished(self, event: Event) -> None:
49
+ self._read_counter.add(len(event.metadata["keys"]))
50
+
51
+ def _on_write_finished(self, event: Event) -> None:
52
+ self._write_counter.add(len(event.metadata["keys"]))
53
+
54
+ def _on_evicted(self, event: Event) -> None:
55
+ self._evicted_counter.add(len(event.metadata["keys"]))
@@ -0,0 +1,166 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """L1 chunk lifecycle subscriber — OTel histograms for L1 chunk lifecycle.
4
+
5
+ Separated from L1MetricsSubscriber (counters) so that users can enable/disable
6
+ lifecycle tracking independently. The shadow map and sampling overhead are
7
+ non-negligible, so this subscriber should only be registered when lifecycle
8
+ metrics are needed.
9
+ """
10
+
11
+ # Future
12
+ from __future__ import annotations
13
+
14
+ # Standard
15
+ from dataclasses import dataclass
16
+ from typing import Any
17
+ import time
18
+
19
+ # Third Party
20
+ from opentelemetry import metrics
21
+
22
+ # First Party
23
+ from lmcache.v1.mp_observability.event import Event, EventType
24
+ from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
25
+
26
+
27
+ @dataclass
28
+ class _L1ChunkState:
29
+ """Per-chunk lifecycle state in the shadow map."""
30
+
31
+ alloc_time: float
32
+ last_access_time: float
33
+
34
+
35
+ class L1LifecycleSubscriber(EventSubscriber):
36
+ """Tracks L1 chunk lifecycle via shadow monitoring.
37
+
38
+ Histograms (chunk lifecycle):
39
+ - ``lmcache_mp.l1_chunk_lifetime_seconds`` — allocation to eviction
40
+ - ``lmcache_mp.l1_chunk_idle_before_evict_seconds`` — last access to eviction
41
+ - ``lmcache_mp.l1_chunk_reuse_gap_seconds`` — gap between consecutive touches
42
+ - ``lmcache_mp.l1_chunk_evict_reuse_gap_seconds`` — eviction to
43
+ next reuse (capped at ``max_evict_reuse_wait``)
44
+
45
+ Parameters:
46
+ sample_rate: Fraction of chunks to track (0, 1.0]. Default 0.01 (1%).
47
+ max_evict_reuse_wait: Maximum seconds to track an evicted chunk
48
+ waiting for reuse. Default 300 s (5 min).
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ sample_rate: float = 0.01,
54
+ max_evict_reuse_wait: float = 300.0,
55
+ ) -> None:
56
+ assert 0 < sample_rate <= 1.0, (
57
+ f"sample_rate must be in (0, 1.0], got {sample_rate}"
58
+ )
59
+ self._sample_rate = sample_rate
60
+ self._max_evict_reuse_wait = max_evict_reuse_wait
61
+ # Deterministic sampling via hash: hash(key) % _SAMPLE_PRIME < threshold.
62
+ # O(1) memory, no set growth, same key always gets the same decision.
63
+ self._sample_prime = 1_000_003
64
+ self._sample_threshold = int(sample_rate * self._sample_prime)
65
+ meter = metrics.get_meter("lmcache.l1")
66
+ self._lifetime_hist = meter.create_histogram(
67
+ "lmcache_mp.l1_chunk_lifetime_seconds",
68
+ description=(
69
+ "Histogram of L1 chunk lifetime from allocation to eviction (seconds)."
70
+ ),
71
+ unit="s",
72
+ )
73
+ self._idle_hist = meter.create_histogram(
74
+ "lmcache_mp.l1_chunk_idle_before_evict_seconds",
75
+ description=("Histogram of idle time before L1 chunk eviction (seconds)."),
76
+ unit="s",
77
+ )
78
+ self._reuse_gap_hist = meter.create_histogram(
79
+ "lmcache_mp.l1_chunk_reuse_gap_seconds",
80
+ description=(
81
+ "Histogram of time gaps between consecutive "
82
+ "touches (write or read) of the same L1 chunk (seconds)."
83
+ ),
84
+ unit="s",
85
+ )
86
+ self._evict_reuse_gap_hist = meter.create_histogram(
87
+ "lmcache_mp.l1_chunk_evict_reuse_gap_seconds",
88
+ description=(
89
+ "Histogram of time from L1 chunk eviction to "
90
+ "next reuse. Capped at max_evict_reuse_wait."
91
+ ),
92
+ unit="s",
93
+ )
94
+
95
+ # Shadow map: key -> chunk lifecycle state (live chunks).
96
+ self._shadow: dict[Any, _L1ChunkState] = {}
97
+ # Evicted map: key -> eviction timestamp (waiting for reuse).
98
+ self._evicted_at: dict[Any, float] = {}
99
+
100
+ def get_subscriptions(self) -> dict[EventType, EventCallback]:
101
+ return {
102
+ EventType.L1_READ_FINISHED: self._on_read_finished,
103
+ EventType.L1_WRITE_FINISHED: self._on_write_finished,
104
+ EventType.L1_WRITE_FINISHED_AND_READ_RESERVED: self._on_write_finished,
105
+ EventType.L1_KEYS_EVICTED: self._on_evicted,
106
+ }
107
+
108
+ def _on_read_finished(self, event: Event) -> None:
109
+ now = event.timestamp or time.time()
110
+ for key in event.metadata["keys"]:
111
+ state = self._shadow.get(key)
112
+ if state is not None:
113
+ self._reuse_gap_hist.record(now - state.last_access_time)
114
+ state.last_access_time = now
115
+
116
+ def _on_write_finished(self, event: Event) -> None:
117
+ now = event.timestamp or time.time()
118
+ for key in event.metadata["keys"]:
119
+ # Check if this is a reuse of an evicted chunk.
120
+ evict_time = self._evicted_at.pop(key, None)
121
+ if evict_time is not None:
122
+ gap = min(now - evict_time, self._max_evict_reuse_wait)
123
+ self._evict_reuse_gap_hist.record(gap)
124
+
125
+ state = self._shadow.get(key)
126
+ if state is not None:
127
+ # Re-write of existing chunk counts as a touch.
128
+ self._reuse_gap_hist.record(now - state.last_access_time)
129
+ self._shadow[key] = _L1ChunkState(
130
+ alloc_time=now,
131
+ last_access_time=now,
132
+ )
133
+ else:
134
+ # First time seeing this key — deterministic sample check.
135
+ if not self._should_sample(key):
136
+ continue
137
+ self._shadow[key] = _L1ChunkState(
138
+ alloc_time=now,
139
+ last_access_time=now,
140
+ )
141
+ self._sweep_stale_evictions(now)
142
+
143
+ def _on_evicted(self, event: Event) -> None:
144
+ now = event.timestamp or time.time()
145
+ for key in event.metadata["keys"]:
146
+ state = self._shadow.pop(key, None)
147
+ if state is not None:
148
+ self._lifetime_hist.record(now - state.alloc_time)
149
+ self._idle_hist.record(now - state.last_access_time)
150
+ # Start tracking eviction-to-reuse gap (only for sampled).
151
+ self._evicted_at[key] = now
152
+ self._sweep_stale_evictions(now)
153
+
154
+ def _should_sample(self, key: object) -> bool:
155
+ return hash(key) % self._sample_prime < self._sample_threshold
156
+
157
+ def _sweep_stale_evictions(self, now: float) -> None:
158
+ """Report T and discard evicted entries older than max_evict_reuse_wait."""
159
+ stale = [
160
+ key
161
+ for key, evict_time in self._evicted_at.items()
162
+ if now - evict_time >= self._max_evict_reuse_wait
163
+ ]
164
+ for key in stale:
165
+ self._evicted_at.pop(key, None)
166
+ self._evict_reuse_gap_hist.record(self._max_evict_reuse_wait)
@@ -0,0 +1,121 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """L2 storage metrics subscriber — OTel counters for L2 store/prefetch events."""
4
+
5
+ # Future
6
+ from __future__ import annotations
7
+
8
+ # Third Party
9
+ from opentelemetry import metrics
10
+
11
+ # First Party
12
+ from lmcache.v1.mp_observability.event import Event, EventType
13
+ from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
14
+
15
+
16
+ class L2MetricsSubscriber(EventSubscriber):
17
+ """Maintains OTel counters for L2 store and prefetch operations.
18
+
19
+ Metrics:
20
+ - ``lmcache_mp.l2_store_tasks`` — store tasks submitted to L2
21
+ - ``lmcache_mp.l2_store_keys`` — keys submitted for L2 store
22
+ - ``lmcache_mp.l2_store_completed`` — store tasks completed
23
+ - ``lmcache_mp.l2_store_succeeded_keys`` — keys successfully stored to L2
24
+ - ``lmcache_mp.l2_store_failed_keys`` — keys that failed to store to L2
25
+ - ``lmcache_mp.l2_prefetch_lookups`` — prefetch lookup requests
26
+ - ``lmcache_mp.l2_prefetch_lookup_keys`` — keys submitted for lookup
27
+ - ``lmcache_mp.l2_prefetch_hit_keys`` — prefix keys found in L2
28
+ - ``lmcache_mp.l2_prefetch_load_tasks`` — load tasks submitted
29
+ - ``lmcache_mp.l2_prefetch_load_keys`` — keys submitted for load
30
+ - ``lmcache_mp.l2_prefetch_loaded_keys`` — keys successfully loaded from L2
31
+ - ``lmcache_mp.l2_prefetch_failed_keys`` — keys that failed to load
32
+ """
33
+
34
+ def __init__(self) -> None:
35
+ meter = metrics.get_meter("lmcache.l2")
36
+
37
+ # Store counters
38
+ self._store_tasks = meter.create_counter(
39
+ "lmcache_mp.l2_store_tasks",
40
+ description="Total L2 store tasks submitted",
41
+ )
42
+ self._store_keys = meter.create_counter(
43
+ "lmcache_mp.l2_store_keys",
44
+ description="Total keys submitted for L2 store",
45
+ )
46
+ self._store_completed = meter.create_counter(
47
+ "lmcache_mp.l2_store_completed",
48
+ description="Total L2 store tasks completed",
49
+ )
50
+ self._store_succeeded_keys = meter.create_counter(
51
+ "lmcache_mp.l2_store_succeeded_keys",
52
+ description="Total keys successfully stored to L2",
53
+ )
54
+ self._store_failed_keys = meter.create_counter(
55
+ "lmcache_mp.l2_store_failed_keys",
56
+ description="Total keys that failed to store to L2",
57
+ )
58
+
59
+ # Prefetch counters
60
+ self._prefetch_lookups = meter.create_counter(
61
+ "lmcache_mp.l2_prefetch_lookups",
62
+ description="Total L2 prefetch lookup requests",
63
+ )
64
+ self._prefetch_lookup_keys = meter.create_counter(
65
+ "lmcache_mp.l2_prefetch_lookup_keys",
66
+ description="Total keys submitted for L2 prefetch lookup",
67
+ )
68
+ self._prefetch_hit_keys = meter.create_counter(
69
+ "lmcache_mp.l2_prefetch_hit_keys",
70
+ description="Total prefix keys found in L2 lookup",
71
+ )
72
+ self._prefetch_load_tasks = meter.create_counter(
73
+ "lmcache_mp.l2_prefetch_load_tasks",
74
+ description="Total L2 prefetch load tasks submitted",
75
+ )
76
+ self._prefetch_load_keys = meter.create_counter(
77
+ "lmcache_mp.l2_prefetch_load_keys",
78
+ description="Total keys submitted for L2 load",
79
+ )
80
+ self._prefetch_loaded_keys = meter.create_counter(
81
+ "lmcache_mp.l2_prefetch_loaded_keys",
82
+ description="Total keys successfully loaded from L2",
83
+ )
84
+ self._prefetch_failed_keys = meter.create_counter(
85
+ "lmcache_mp.l2_prefetch_failed_keys",
86
+ description="Total keys that failed to load from L2",
87
+ )
88
+
89
+ def get_subscriptions(self) -> dict[EventType, EventCallback]:
90
+ return {
91
+ EventType.L2_STORE_SUBMITTED: self._on_store_submitted,
92
+ EventType.L2_STORE_COMPLETED: self._on_store_completed,
93
+ EventType.L2_PREFETCH_LOOKUP_SUBMITTED: self._on_lookup_submitted,
94
+ EventType.L2_PREFETCH_LOOKUP_COMPLETED: self._on_lookup_completed,
95
+ EventType.L2_PREFETCH_LOAD_SUBMITTED: self._on_load_submitted,
96
+ EventType.L2_PREFETCH_LOAD_COMPLETED: self._on_load_completed,
97
+ }
98
+
99
+ def _on_store_submitted(self, event: Event) -> None:
100
+ self._store_tasks.add(1)
101
+ self._store_keys.add(event.metadata["key_count"])
102
+
103
+ def _on_store_completed(self, event: Event) -> None:
104
+ self._store_completed.add(1)
105
+ self._store_succeeded_keys.add(event.metadata["succeeded_count"])
106
+ self._store_failed_keys.add(event.metadata["failed_count"])
107
+
108
+ def _on_lookup_submitted(self, event: Event) -> None:
109
+ self._prefetch_lookups.add(1)
110
+ self._prefetch_lookup_keys.add(event.metadata["key_count"])
111
+
112
+ def _on_lookup_completed(self, event: Event) -> None:
113
+ self._prefetch_hit_keys.add(event.metadata["prefix_hit_count"])
114
+
115
+ def _on_load_submitted(self, event: Event) -> None:
116
+ self._prefetch_load_tasks.add(event.metadata["adapter_count"])
117
+ self._prefetch_load_keys.add(event.metadata["key_count"])
118
+
119
+ def _on_load_completed(self, event: Event) -> None:
120
+ self._prefetch_loaded_keys.add(event.metadata["loaded_count"])
121
+ self._prefetch_failed_keys.add(event.metadata["failed_count"])