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,260 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # TODO(baoloongmao): This module contains the control plane implementation
4
+ # for socket-based channels. Currently, it provides the base class with
5
+ # ZMQ-based handshake and initialization logic. In the future, this can
6
+ # be extended to a complete channel with data plane using native sockets.
7
+
8
+ # Standard
9
+ from typing import Optional, Union
10
+ import asyncio
11
+ import threading
12
+ import time
13
+
14
+ # Third Party
15
+ import msgspec
16
+ import zmq
17
+
18
+ # First Party
19
+ from lmcache.logging import init_logger
20
+ from lmcache.v1.rpc_utils import get_zmq_context, get_zmq_socket
21
+ from lmcache.v1.transfer_channel.abstract import BaseTransferChannel
22
+ from lmcache.v1.transfer_channel.transfer_utils import (
23
+ InitSideMsgBase,
24
+ InitSideRetMsgBase,
25
+ SideMsg,
26
+ )
27
+
28
+ logger = init_logger(__name__)
29
+
30
+
31
+ class PySocketMsgBase(msgspec.Struct, tag=True):
32
+ """Base class for all py-socket-related messages"""
33
+
34
+ pass
35
+
36
+
37
+ class PySocketInitRequest(PySocketMsgBase):
38
+ """Initialization request, peer_init_url is used as peer identifier"""
39
+
40
+ peer_init_url: str
41
+
42
+
43
+ class PySocketInitResponse(PySocketMsgBase):
44
+ """Initialization response"""
45
+
46
+ status: str
47
+
48
+
49
+ PySocketMsg = Union[
50
+ PySocketInitRequest,
51
+ PySocketInitResponse,
52
+ ]
53
+
54
+
55
+ class PySocketChannel(BaseTransferChannel):
56
+ """
57
+ Base class for socket-based transfer channels.
58
+
59
+ Control plane: Uses ZMQ sockets for handshake and initialization.
60
+ Data plane: To be implemented by subclasses.
61
+
62
+ Provides common control plane logic for different channel types.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ async_mode: bool = False,
68
+ **kwargs,
69
+ ):
70
+ assert "role" in kwargs
71
+ assert "buffer_ptr" in kwargs
72
+ assert "buffer_size" in kwargs
73
+ assert "align_bytes" in kwargs
74
+ assert "tp_rank" in kwargs
75
+ assert "peer_init_url" in kwargs
76
+
77
+ self.role = kwargs["role"]
78
+ self.buffer_ptr = kwargs["buffer_ptr"]
79
+ self.buffer_size = kwargs["buffer_size"]
80
+ self.align_bytes = kwargs["align_bytes"]
81
+ self.tp_rank = kwargs["tp_rank"]
82
+
83
+ self.peer_lookup_url = kwargs.get("peer_lookup_url", None)
84
+
85
+ self.running = True
86
+ self.remote_connections: dict[str, dict] = {}
87
+
88
+ self.side_channel: Optional[zmq.Socket] = None
89
+ self.running_threads: list[threading.Thread] = []
90
+
91
+ self.async_mode = async_mode
92
+ if self.async_mode:
93
+ self.zmq_context = get_zmq_context(use_asyncio=True)
94
+ else:
95
+ self.zmq_context = get_zmq_context(use_asyncio=False)
96
+ self.peer_init_url = kwargs["peer_init_url"]
97
+ self.event_loop = kwargs.get("event_loop", None)
98
+
99
+ self._init_side_channels()
100
+
101
+ ############################################################
102
+ # Control plane: Initialization functions
103
+ ############################################################
104
+
105
+ def lazy_init_peer_connection(
106
+ self,
107
+ local_id: str,
108
+ peer_id: str,
109
+ peer_init_url: str,
110
+ init_side_msg: Optional[InitSideMsgBase] = None,
111
+ ) -> Optional[InitSideRetMsgBase]:
112
+ raise NotImplementedError("Sync mode not supported in PySocketChannel")
113
+
114
+ async def async_lazy_init_peer_connection(
115
+ self,
116
+ local_id: str,
117
+ peer_id: str,
118
+ peer_init_url: str,
119
+ init_side_msg: Optional[InitSideMsgBase] = None,
120
+ ) -> Optional[InitSideRetMsgBase]:
121
+ """
122
+ Initialize connection to a peer using ZMQ sockets for handshake.
123
+
124
+ Note: peer_id is expected to be peer_init_url in this implementation.
125
+ """
126
+ init_tmp_socket = get_zmq_socket(
127
+ self.zmq_context,
128
+ peer_init_url,
129
+ "tcp",
130
+ zmq.REQ,
131
+ "connect",
132
+ )
133
+
134
+ init_req = PySocketInitRequest(peer_init_url=self.peer_init_url)
135
+ await init_tmp_socket.send(msgspec.msgpack.encode(init_req))
136
+
137
+ init_resp_bytes = await init_tmp_socket.recv()
138
+ _ = msgspec.msgpack.decode(init_resp_bytes, type=PySocketMsg)
139
+
140
+ self.remote_connections[peer_id] = {
141
+ "peer_init_url": peer_init_url,
142
+ }
143
+
144
+ init_ret_msg: Optional[InitSideRetMsgBase] = None
145
+ if init_side_msg is not None:
146
+ init_ret_msg = await self.async_send_init_side_msg(
147
+ init_tmp_socket,
148
+ init_side_msg,
149
+ )
150
+
151
+ init_tmp_socket.close()
152
+ return init_ret_msg
153
+
154
+ def _init_side_channels(self):
155
+ """Initialize side channel for handling incoming connections"""
156
+ if self.peer_init_url is None:
157
+ return
158
+
159
+ self.side_channel = get_zmq_socket(
160
+ self.zmq_context,
161
+ self.peer_init_url,
162
+ "tcp",
163
+ zmq.REP,
164
+ "bind",
165
+ )
166
+
167
+ if self.async_mode:
168
+ asyncio.run_coroutine_threadsafe(self._async_init_loop(), self.event_loop)
169
+ else:
170
+ self.init_thread = threading.Thread(target=self._init_loop, daemon=True)
171
+ self.init_thread.start()
172
+ self.running_threads.append(self.init_thread)
173
+
174
+ def _handle_init_msg(
175
+ self, req: Union[PySocketMsg, InitSideMsgBase]
176
+ ) -> Union[PySocketMsg, InitSideRetMsgBase]:
177
+ """Handle initialization messages from peers"""
178
+ resp: Union[PySocketMsg, InitSideRetMsgBase]
179
+ if isinstance(req, PySocketInitRequest):
180
+ peer_url = req.peer_init_url
181
+
182
+ self.remote_connections[peer_url] = {
183
+ "peer_init_url": peer_url,
184
+ }
185
+
186
+ self._on_peer_connected(peer_url)
187
+
188
+ resp = PySocketInitResponse(status="ok")
189
+ logger.info("Replying initialization response")
190
+
191
+ elif isinstance(req, InitSideMsgBase):
192
+ resp = self.handle_init_side_msg(req)
193
+ logger.info("Replying P2P init side response")
194
+ else:
195
+ raise ValueError(f"Unsupported InitMsg type: {type(req)}")
196
+
197
+ return resp
198
+
199
+ def _on_peer_connected(self, peer_url: str):
200
+ """Hook for subclasses to perform additional setup when a peer connects"""
201
+ pass
202
+
203
+ def _init_loop(self):
204
+ """Synchronous initialization loop for handling incoming connections"""
205
+ while self.running:
206
+ try:
207
+ req_bytes = self.side_channel.recv()
208
+
209
+ logger.info("Received initialization request")
210
+
211
+ req = msgspec.msgpack.decode(
212
+ req_bytes, type=Union[PySocketMsg, SideMsg]
213
+ )
214
+
215
+ resp = self._handle_init_msg(req)
216
+
217
+ self.side_channel.send(msgspec.msgpack.encode(resp))
218
+
219
+ except Exception as e:
220
+ logger.error("Failed to process initialization loop: %s", str(e))
221
+ if self.running:
222
+ time.sleep(0.01)
223
+
224
+ async def _async_init_loop(self):
225
+ """Asynchronous initialization loop for handling incoming connections"""
226
+ logger.info("Starting async initialization loop")
227
+
228
+ while self.running:
229
+ try:
230
+ req_bytes = await self.side_channel.recv()
231
+
232
+ logger.info("Received initialization request")
233
+
234
+ req = msgspec.msgpack.decode(
235
+ req_bytes, type=Union[PySocketMsg, SideMsg]
236
+ )
237
+
238
+ resp = self._handle_init_msg(req)
239
+
240
+ await self.side_channel.send(msgspec.msgpack.encode(resp))
241
+
242
+ except Exception as e:
243
+ logger.error("Failed to process initialization loop: %s", str(e))
244
+ if self.running:
245
+ await asyncio.sleep(0.01)
246
+
247
+ ############################################################
248
+ # Cleanup-related functions
249
+ ############################################################
250
+
251
+ def close(self):
252
+ """Close all sockets and cleanup resources"""
253
+ self.running = False
254
+ for thread in self.running_threads:
255
+ thread.join()
256
+
257
+ if self.side_channel is not None:
258
+ self.side_channel.close()
259
+
260
+ self.zmq_context.term()
@@ -0,0 +1,63 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from typing import Union
4
+
5
+ # Third Party
6
+ import msgspec
7
+
8
+
9
+ def get_correct_device(device: str, worker_id: int) -> str:
10
+ """
11
+ Get the correct device based on the given device string.
12
+
13
+ Args:
14
+ device (str): The device string, could be cpu or cuda.
15
+ worker_id (int): The worker id to determine the cuda device.
16
+
17
+ Returns:
18
+ str: The correct device string with device id.
19
+ """
20
+ if device == "cpu":
21
+ return "cpu"
22
+ elif device.startswith("cuda"):
23
+ return f"cuda:{worker_id}"
24
+ else:
25
+ raise ValueError(f"Invalid device: {device}")
26
+
27
+
28
+ class SideMsgBase(msgspec.Struct, tag=True):
29
+ """Base class for all side-related messages during initialization"""
30
+
31
+ pass
32
+
33
+
34
+ # Side messages during initialization
35
+ class InitSideMsgBase(SideMsgBase):
36
+ """Base class for all side-related messages during initialization"""
37
+
38
+ pass
39
+
40
+
41
+ class P2PInitSideMsg(InitSideMsgBase):
42
+ """P2P specific initialization message"""
43
+
44
+ pass
45
+
46
+
47
+ # Side return messages during initialization
48
+ class InitSideRetMsgBase(SideMsgBase):
49
+ """Base class for all side-related messages during initialization"""
50
+
51
+ pass
52
+
53
+
54
+ class P2PInitSideRetMsg(InitSideRetMsgBase):
55
+ """P2P specific initialization return message"""
56
+
57
+ peer_lookup_url: str
58
+
59
+
60
+ SideMsg = Union[
61
+ P2PInitSideMsg,
62
+ P2PInitSideRetMsg,
63
+ ]
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,109 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ # Standard
4
+ from typing import Union
5
+ import array
6
+ import hashlib
7
+ import math
8
+
9
+ # First Party
10
+ from lmcache.logging import init_logger
11
+
12
+ logger = init_logger(__name__)
13
+
14
+
15
+ class BloomFilter:
16
+ """Bloom Filter for memory-efficient."""
17
+
18
+ def __init__(
19
+ self, expected_elements: int = 1000000, false_positive_rate: float = 0.01
20
+ ):
21
+ self.size = self._optimal_size(expected_elements, false_positive_rate)
22
+ self.hash_count = self._optimal_hash_count(self.size, expected_elements)
23
+ array_size = (self.size + 31) // 32
24
+ self.bit_array = array.array("I", [0] * array_size)
25
+ self.item_count = 0
26
+ self.expected_elements = expected_elements
27
+ self.false_positive_rate = false_positive_rate
28
+
29
+ @staticmethod
30
+ def _optimal_size(n: int, p: float) -> int:
31
+ return int(-(n * math.log(p)) / (math.log(2) ** 2))
32
+
33
+ @staticmethod
34
+ def _optimal_hash_count(m: int, n: int) -> int:
35
+ return max(1, int((m / n) * math.log(2)))
36
+
37
+ def _hashes(self, item: Union[str, int]) -> list[int]:
38
+ result = []
39
+ if isinstance(item, int):
40
+ for i in range(self.hash_count):
41
+ h = hashlib.sha256(
42
+ item.to_bytes((item.bit_length() + 7) // 8, "big", signed=False)
43
+ + i.to_bytes(4, "big")
44
+ ).digest()
45
+ result.append(int.from_bytes(h[:4], "big") % self.size)
46
+ else:
47
+ for i in range(self.hash_count):
48
+ h = hashlib.sha256(f"{item}_{i}".encode()).digest()
49
+ result.append(int.from_bytes(h[:4], "big") % self.size)
50
+ return result
51
+
52
+ def add(self, item: Union[str, int]) -> None:
53
+ for pos in self._hashes(item):
54
+ self.bit_array[pos >> 5] |= 1 << (pos & 31)
55
+ self.item_count += 1
56
+
57
+ def add_batch_with_hashes_and_check(self, positions_list: list[list[int]]) -> int:
58
+ """Add multiple sets of hash positions and return count of new items.
59
+
60
+ Args:
61
+ positions_list: List of lists, where each inner list contains
62
+ hash positions for one item
63
+
64
+ Returns:
65
+ Number of new items that were actually added
66
+ """
67
+ unique_count = 0
68
+ for positions in positions_list:
69
+ is_new = any(
70
+ not (self.bit_array[pos >> 5] & (1 << (pos & 31))) for pos in positions
71
+ )
72
+ if is_new:
73
+ for pos in positions:
74
+ self.bit_array[pos >> 5] |= 1 << (pos & 31)
75
+ unique_count += 1
76
+ self.item_count += unique_count
77
+ return unique_count
78
+
79
+ def contains(self, item: Union[str, int]) -> bool:
80
+ for pos in self._hashes(item):
81
+ if not (self.bit_array[pos >> 5] & (1 << (pos & 31))):
82
+ return False
83
+ return True
84
+
85
+ def clear(self) -> None:
86
+ self.bit_array = array.array("I", [0] * ((self.size + 31) // 32))
87
+ self.item_count = 0
88
+
89
+ def get_memory_usage_bytes(self) -> int:
90
+ return self.size // 8
91
+
92
+ def get_bit_set(self) -> int:
93
+ return sum(val.bit_count() for val in self.bit_array)
94
+
95
+ def get_fill_rate(self) -> float:
96
+ bits_set = self.get_bit_set()
97
+ return bits_set / self.size if self.size > 0 else 0.0
98
+
99
+ def get_statistics(self) -> dict:
100
+ bits_set = self.get_bit_set()
101
+ return {
102
+ "size_mb": self.get_memory_usage_bytes() / 1024 / 1024,
103
+ "hash_count": self.hash_count,
104
+ "item_count": self.item_count,
105
+ "bits_set": bits_set,
106
+ "fill_rate": bits_set / self.size if self.size > 0 else 0.0,
107
+ "expected_elements": self.expected_elements,
108
+ "false_positive_rate": self.false_positive_rate,
109
+ }
@@ -0,0 +1,125 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from typing import Callable, Generic, Optional, TypeVar
4
+ import threading
5
+ import time
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class TTLListCache(Generic[T]):
11
+ """
12
+ Generic list cache with TTL (Time-To-Live) support.
13
+
14
+ This cache stores a list of items of type T and automatically refreshes
15
+ when the cache expires.
16
+ """
17
+
18
+ def __init__(self, timeout_seconds: float = 30.0):
19
+ """
20
+ Initialize the cache.
21
+
22
+ Args:
23
+ timeout_seconds: Default TTL in seconds for cache entries.
24
+ """
25
+ self.cache: list[T] = []
26
+ self.cache_time: float = 0.0
27
+ self.cache_lock = threading.Lock()
28
+ self.timeout_seconds = timeout_seconds
29
+
30
+ def get_cached(
31
+ self,
32
+ get_fresh_data: Callable[[], list[T]],
33
+ timeout_override: Optional[float] = None,
34
+ ) -> list[T]:
35
+ """
36
+ Get cached data, refreshing if expired.
37
+
38
+ Args:
39
+ get_fresh_data: Function to get fresh data when cache is expired
40
+ timeout_override: Optional timeout override in seconds.
41
+ If None, use instance timeout_seconds.
42
+ If 0: cache immediately expires, always get fresh data and update cache.
43
+ If > 0: use specified timeout.
44
+
45
+ Returns:
46
+ List of cached items, either from cache or fresh data
47
+ """
48
+ current_time = time.time()
49
+
50
+ # Fast path: check cache without lock
51
+ if not self.is_expired(timeout_override, current_time):
52
+ return self.cache
53
+
54
+ # Slow path: refresh cache with lock
55
+ with self.cache_lock:
56
+ # Double-check after acquiring lock
57
+ if not self.is_expired(timeout_override, current_time):
58
+ return self.cache
59
+
60
+ # Refresh cache
61
+ self.cache = get_fresh_data()
62
+ self.cache_time = current_time
63
+ return self.cache
64
+
65
+ def clear(self) -> None:
66
+ """Clear the cache."""
67
+ with self.cache_lock:
68
+ self.cache.clear()
69
+ self.cache_time = 0.0
70
+
71
+ def is_expired(
72
+ self,
73
+ timeout_override: Optional[float] = None,
74
+ current_time: Optional[float] = None,
75
+ ) -> bool:
76
+ """
77
+ Check if the cache is expired without refreshing it.
78
+
79
+ Args:
80
+ timeout_override: Optional timeout override in seconds.
81
+ If None, use instance timeout_seconds.
82
+ If 0: always considered expired.
83
+ If > 0: use specified timeout.
84
+ current_time: Optional current time. If None, use time.time().
85
+
86
+ Returns:
87
+ True if cache is expired or uninitialized, False otherwise
88
+ """
89
+ if self.cache_time == 0.0:
90
+ return True
91
+
92
+ effective_timeout = (
93
+ timeout_override if timeout_override is not None else self.timeout_seconds
94
+ )
95
+ if current_time is None:
96
+ current_time = time.time()
97
+
98
+ return current_time - self.cache_time >= effective_timeout
99
+
100
+ def get_cache_age(self) -> float:
101
+ """
102
+ Get the age of the cache in seconds.
103
+
104
+ Returns:
105
+ Age in seconds. Returns float('inf') if cache is uninitialized.
106
+ """
107
+ if self.cache_time == 0.0:
108
+ return float("inf")
109
+ return time.time() - self.cache_time
110
+
111
+ def __len__(self) -> int:
112
+ """Get the number of items in cache (without checking expiration)."""
113
+ return len(self.cache)
114
+
115
+ def __repr__(self) -> str:
116
+ """String representation of the cache."""
117
+ age = self.get_cache_age()
118
+ if age == float("inf"):
119
+ age_str = "uninitialized"
120
+ else:
121
+ age_str = f"{age:.2f}s old"
122
+ return (
123
+ f"TTLListCache(cache_items={len(self)}, age={age_str}, "
124
+ f"timeout={self.timeout_seconds}s)"
125
+ )