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.
- lmcache/__init__.py +84 -0
- lmcache/_version.py +24 -0
- lmcache/cli/__init__.py +1 -0
- lmcache/cli/commands/__init__.py +34 -0
- lmcache/cli/commands/base.py +157 -0
- lmcache/cli/commands/bench/__init__.py +557 -0
- lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
- lmcache/cli/commands/bench/engine_bench/config.py +245 -0
- lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
- lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
- lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
- lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
- lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
- lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
- lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
- lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
- lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
- lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
- lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
- lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
- lmcache/cli/commands/describe.py +310 -0
- lmcache/cli/commands/kvcache.py +133 -0
- lmcache/cli/commands/mock.py +75 -0
- lmcache/cli/commands/ping.py +113 -0
- lmcache/cli/commands/query/__init__.py +155 -0
- lmcache/cli/commands/query/prompt.py +134 -0
- lmcache/cli/commands/query/request.py +357 -0
- lmcache/cli/commands/server.py +99 -0
- lmcache/cli/commands/tool/__init__.py +63 -0
- lmcache/cli/commands/tool/cache_simulator.py +113 -0
- lmcache/cli/commands/trace/__init__.py +505 -0
- lmcache/cli/commands/trace/dispatch.py +249 -0
- lmcache/cli/commands/trace/driver.py +372 -0
- lmcache/cli/commands/trace/stats.py +289 -0
- lmcache/cli/documents/lmcache.txt +11 -0
- lmcache/cli/main.py +42 -0
- lmcache/cli/metrics/__init__.py +29 -0
- lmcache/cli/metrics/formatter.py +171 -0
- lmcache/cli/metrics/handler.py +94 -0
- lmcache/cli/metrics/metrics.py +161 -0
- lmcache/cli/metrics/section.py +77 -0
- lmcache/connections.py +173 -0
- lmcache/integration/__init__.py +2 -0
- lmcache/integration/base_service_factory.py +165 -0
- lmcache/integration/request_telemetry/__init__.py +1 -0
- lmcache/integration/request_telemetry/base.py +51 -0
- lmcache/integration/request_telemetry/factory.py +113 -0
- lmcache/integration/request_telemetry/fastapi.py +109 -0
- lmcache/integration/request_telemetry/noop.py +35 -0
- lmcache/integration/sglang/__init__.py +2 -0
- lmcache/integration/sglang/sglang_adapter.py +326 -0
- lmcache/integration/sglang/utils.py +39 -0
- lmcache/integration/vllm/__init__.py +1 -0
- lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
- lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
- lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
- lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
- lmcache/integration/vllm/utils.py +433 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
- lmcache/integration/vllm/vllm_service_factory.py +339 -0
- lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
- lmcache/logging.py +107 -0
- lmcache/native_storage_ops.pyi +230 -0
- lmcache/non_cuda_equivalents.py +1424 -0
- lmcache/observability.py +1958 -0
- lmcache/storage_backend/serde/__init__.py +1 -0
- lmcache/storage_backend/serde/cachegen_basics.py +210 -0
- lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
- lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
- lmcache/storage_backend/serde/serde.py +75 -0
- lmcache/tools/__init__.py +1 -0
- lmcache/tools/cache_simulator/README.md +392 -0
- lmcache/tools/cache_simulator/__init__.py +1 -0
- lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
- lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
- lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
- lmcache/tools/cache_simulator/lru_cache.py +124 -0
- lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
- lmcache/tools/cache_simulator/simulator.py +795 -0
- lmcache/tools/controller_benchmark/README.md +161 -0
- lmcache/tools/controller_benchmark/__init__.py +1 -0
- lmcache/tools/controller_benchmark/__main__.py +331 -0
- lmcache/tools/controller_benchmark/benchmark.py +660 -0
- lmcache/tools/controller_benchmark/config.py +44 -0
- lmcache/tools/controller_benchmark/constants.py +10 -0
- lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
- lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
- lmcache/tools/controller_benchmark/handlers/base.py +47 -0
- lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
- lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
- lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
- lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
- lmcache/tools/controller_benchmark/handlers/register.py +56 -0
- lmcache/tools/mp_status_viewer/__init__.py +1 -0
- lmcache/tools/mp_status_viewer/__main__.py +95 -0
- lmcache/usage_context.py +417 -0
- lmcache/utils.py +665 -0
- lmcache/v1/__init__.py +2 -0
- lmcache/v1/api_server/__init__.py +2 -0
- lmcache/v1/api_server/__main__.py +537 -0
- lmcache/v1/basic_check.py +112 -0
- lmcache/v1/cache_controller/__init__.py +9 -0
- lmcache/v1/cache_controller/commands/__init__.py +15 -0
- lmcache/v1/cache_controller/commands/base.py +35 -0
- lmcache/v1/cache_controller/commands/full_sync.py +49 -0
- lmcache/v1/cache_controller/config.py +176 -0
- lmcache/v1/cache_controller/controller_manager.py +535 -0
- lmcache/v1/cache_controller/controllers/__init__.py +11 -0
- lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
- lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
- lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
- lmcache/v1/cache_controller/executor.py +463 -0
- lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
- lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
- lmcache/v1/cache_controller/frontend/static/index.html +234 -0
- lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
- lmcache/v1/cache_controller/full_sync_sender.py +475 -0
- lmcache/v1/cache_controller/locks.py +149 -0
- lmcache/v1/cache_controller/message.py +828 -0
- lmcache/v1/cache_controller/observability.py +208 -0
- lmcache/v1/cache_controller/utils.py +679 -0
- lmcache/v1/cache_controller/worker.py +665 -0
- lmcache/v1/cache_engine.py +2058 -0
- lmcache/v1/cache_interface.py +19 -0
- lmcache/v1/check/__init__.py +74 -0
- lmcache/v1/check/check_mode_gen.py +86 -0
- lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
- lmcache/v1/check/check_mode_test_remote.py +155 -0
- lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
- lmcache/v1/check/utils.py +571 -0
- lmcache/v1/compute/__init__.py +2 -0
- lmcache/v1/compute/attention/__init__.py +0 -0
- lmcache/v1/compute/attention/abstract.py +39 -0
- lmcache/v1/compute/attention/flash_attn.py +129 -0
- lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
- lmcache/v1/compute/attention/metadata.py +85 -0
- lmcache/v1/compute/attention/utils.py +14 -0
- lmcache/v1/compute/blend/__init__.py +7 -0
- lmcache/v1/compute/blend/blender.py +168 -0
- lmcache/v1/compute/blend/metadata.py +34 -0
- lmcache/v1/compute/blend/utils.py +63 -0
- lmcache/v1/compute/models/__init__.py +0 -0
- lmcache/v1/compute/models/base.py +141 -0
- lmcache/v1/compute/models/llama.py +9 -0
- lmcache/v1/compute/models/qwen3.py +24 -0
- lmcache/v1/compute/models/utils.py +68 -0
- lmcache/v1/compute/positional_encoding.py +199 -0
- lmcache/v1/config.py +848 -0
- lmcache/v1/config_base.py +848 -0
- lmcache/v1/distributed/api.py +248 -0
- lmcache/v1/distributed/config.py +321 -0
- lmcache/v1/distributed/error.py +64 -0
- lmcache/v1/distributed/eviction.py +192 -0
- lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
- lmcache/v1/distributed/eviction_policy/factory.py +27 -0
- lmcache/v1/distributed/eviction_policy/lru.py +244 -0
- lmcache/v1/distributed/eviction_policy/noop.py +50 -0
- lmcache/v1/distributed/internal_api.py +170 -0
- lmcache/v1/distributed/l1_manager.py +835 -0
- lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
- lmcache/v1/distributed/l2_adapters/base.py +360 -0
- lmcache/v1/distributed/l2_adapters/config.py +385 -0
- lmcache/v1/distributed/l2_adapters/factory.py +205 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
- lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
- lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
- lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
- lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
- lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
- lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
- lmcache/v1/distributed/memory_manager.py +179 -0
- lmcache/v1/distributed/storage_controller.py +39 -0
- lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
- lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
- lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
- lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
- lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
- lmcache/v1/distributed/storage_manager.py +532 -0
- lmcache/v1/event_manager.py +145 -0
- lmcache/v1/exceptions/__init__.py +16 -0
- lmcache/v1/gpu_connector/__init__.py +126 -0
- lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
- lmcache/v1/gpu_connector/gpu_ops.py +85 -0
- lmcache/v1/gpu_connector/hpu_connector.py +326 -0
- lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
- lmcache/v1/gpu_connector/utils.py +890 -0
- lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
- lmcache/v1/health_monitor/__init__.py +1 -0
- lmcache/v1/health_monitor/base.py +587 -0
- lmcache/v1/health_monitor/checks/__init__.py +1 -0
- lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
- lmcache/v1/health_monitor/constants.py +36 -0
- lmcache/v1/internal_api_server/__init__.py +0 -0
- lmcache/v1/internal_api_server/api_registry.py +59 -0
- lmcache/v1/internal_api_server/api_server.py +120 -0
- lmcache/v1/internal_api_server/common/__init__.py +1 -0
- lmcache/v1/internal_api_server/common/env_api.py +22 -0
- lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
- lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
- lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
- lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
- lmcache/v1/internal_api_server/common/thread_api.py +63 -0
- lmcache/v1/internal_api_server/controller/__init__.py +1 -0
- lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
- lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
- lmcache/v1/internal_api_server/utils.py +43 -0
- lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
- lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
- lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
- lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
- lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
- lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
- lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
- lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
- lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
- lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
- lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
- lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
- lmcache/v1/kv_layer_groups.py +267 -0
- lmcache/v1/lazy_memory_allocator.py +284 -0
- lmcache/v1/lookup_client/__init__.py +25 -0
- lmcache/v1/lookup_client/abstract_client.py +77 -0
- lmcache/v1/lookup_client/async_lookup_message.py +50 -0
- lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
- lmcache/v1/lookup_client/factory.py +251 -0
- lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
- lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
- lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
- lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
- lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
- lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
- lmcache/v1/lookup_client/record_strategies/base.py +327 -0
- lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
- lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
- lmcache/v1/manager.py +539 -0
- lmcache/v1/memory_management.py +2619 -0
- lmcache/v1/metadata.py +114 -0
- lmcache/v1/mp_observability/AGENTS.override.md +21 -0
- lmcache/v1/mp_observability/README.md +204 -0
- lmcache/v1/mp_observability/config.py +340 -0
- lmcache/v1/mp_observability/event.py +100 -0
- lmcache/v1/mp_observability/event_bus.py +313 -0
- lmcache/v1/mp_observability/otel_init.py +145 -0
- lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
- lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
- lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
- lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
- lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
- lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
- lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
- lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
- lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
- lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
- lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
- lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
- lmcache/v1/mp_observability/trace/__init__.py +50 -0
- lmcache/v1/mp_observability/trace/codecs.py +255 -0
- lmcache/v1/mp_observability/trace/decorator.py +147 -0
- lmcache/v1/mp_observability/trace/format.py +132 -0
- lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
- lmcache/v1/mp_observability/trace/reader.py +167 -0
- lmcache/v1/mp_observability/trace/recorder.py +300 -0
- lmcache/v1/multiprocess/__init__.py +0 -0
- lmcache/v1/multiprocess/affinity_pool.py +102 -0
- lmcache/v1/multiprocess/blend_server_v2.py +891 -0
- lmcache/v1/multiprocess/config.py +253 -0
- lmcache/v1/multiprocess/custom_types.py +281 -0
- lmcache/v1/multiprocess/futures.py +194 -0
- lmcache/v1/multiprocess/gpu_context.py +511 -0
- lmcache/v1/multiprocess/http_server.py +235 -0
- lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
- lmcache/v1/multiprocess/mq.py +732 -0
- lmcache/v1/multiprocess/protocol.py +86 -0
- lmcache/v1/multiprocess/protocols/README.md +213 -0
- lmcache/v1/multiprocess/protocols/__init__.py +127 -0
- lmcache/v1/multiprocess/protocols/base.py +89 -0
- lmcache/v1/multiprocess/protocols/blend.py +109 -0
- lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
- lmcache/v1/multiprocess/protocols/controller.py +53 -0
- lmcache/v1/multiprocess/protocols/debug.py +34 -0
- lmcache/v1/multiprocess/protocols/engine.py +146 -0
- lmcache/v1/multiprocess/protocols/observability.py +39 -0
- lmcache/v1/multiprocess/server.py +1134 -0
- lmcache/v1/multiprocess/session.py +190 -0
- lmcache/v1/multiprocess/token_hasher.py +441 -0
- lmcache/v1/offload_server/__init__.py +17 -0
- lmcache/v1/offload_server/abstract_server.py +37 -0
- lmcache/v1/offload_server/message.py +30 -0
- lmcache/v1/offload_server/zmq_server.py +122 -0
- lmcache/v1/periodic_thread.py +579 -0
- lmcache/v1/pin_monitor.py +246 -0
- lmcache/v1/plugin/__init__.py +0 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
- lmcache/v1/protocol.py +317 -0
- lmcache/v1/rpc/__init__.py +17 -0
- lmcache/v1/rpc/transport.py +105 -0
- lmcache/v1/rpc/zmq_transport.py +213 -0
- lmcache/v1/rpc_utils.py +165 -0
- lmcache/v1/server/__init__.py +2 -0
- lmcache/v1/server/__main__.py +170 -0
- lmcache/v1/server/storage_backend/__init__.py +21 -0
- lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
- lmcache/v1/server/storage_backend/local_backend.py +75 -0
- lmcache/v1/server/utils.py +21 -0
- lmcache/v1/standalone/__init__.py +1 -0
- lmcache/v1/standalone/__main__.py +583 -0
- lmcache/v1/standalone/manager.py +80 -0
- lmcache/v1/standalone/standalone_service_factory.py +86 -0
- lmcache/v1/storage_backend/__init__.py +313 -0
- lmcache/v1/storage_backend/abstract_backend.py +445 -0
- lmcache/v1/storage_backend/audit_backend.py +233 -0
- lmcache/v1/storage_backend/batched_message_sender.py +222 -0
- lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
- lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
- lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
- lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
- lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
- lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
- lmcache/v1/storage_backend/connector/__init__.py +443 -0
- lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
- lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
- lmcache/v1/storage_backend/connector/base_connector.py +379 -0
- lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
- lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
- lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
- lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
- lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
- lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
- lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
- lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
- lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
- lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
- lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
- lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
- lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
- lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
- lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
- lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
- lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
- lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
- lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
- lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
- lmcache/v1/storage_backend/gds_backend.py +1199 -0
- lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
- lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
- lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
- lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
- lmcache/v1/storage_backend/local_disk_backend.py +656 -0
- lmcache/v1/storage_backend/maru_backend.py +734 -0
- lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
- lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
- lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
- lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
- lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
- lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
- lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
- lmcache/v1/storage_backend/p2p_backend.py +788 -0
- lmcache/v1/storage_backend/path_sharder.py +117 -0
- lmcache/v1/storage_backend/pd_backend.py +646 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
- lmcache/v1/storage_backend/remote_backend.py +624 -0
- lmcache/v1/storage_backend/resp_client.py +227 -0
- lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
- lmcache/v1/storage_backend/storage_manager.py +1352 -0
- lmcache/v1/system_detection.py +110 -0
- lmcache/v1/token_database.py +551 -0
- lmcache/v1/transfer_channel/__init__.py +83 -0
- lmcache/v1/transfer_channel/abstract.py +285 -0
- lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
- lmcache/v1/transfer_channel/nixl_channel.py +639 -0
- lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
- lmcache/v1/transfer_channel/transfer_utils.py +63 -0
- lmcache/v1/utils/__init__.py +1 -0
- lmcache/v1/utils/bloom_filter.py +109 -0
- lmcache/v1/utils/cache_utils.py +125 -0
- lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
- lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
- lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
- lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
- lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
- 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"])
|