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,360 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Convert LMCache lookup-hash JSONL logs into a ``vllm bench serve`` custom
|
|
4
|
+
dataset (JSONL with ``"prompt"`` and ``"output_tokens"`` fields).
|
|
5
|
+
|
|
6
|
+
The conversion preserves the **prefix-sharing structure** of the original
|
|
7
|
+
requests: requests that shared a chunk hash in the lookup logs will share the
|
|
8
|
+
same token prefix in the generated prompts. This allows the synthetic
|
|
9
|
+
benchmark to exercise LMCache prefix caching in the same pattern as the
|
|
10
|
+
original production workload.
|
|
11
|
+
|
|
12
|
+
Algorithm
|
|
13
|
+
---------
|
|
14
|
+
1. Build a *safe vocabulary*: token IDs from the tokenizer whose single-token
|
|
15
|
+
round-trip is stable (``decode([id])`` re-encodes back to exactly ``[id]``).
|
|
16
|
+
Tokens that start with a leading space are preferred to avoid BPE merges at
|
|
17
|
+
chunk boundaries.
|
|
18
|
+
2. For each unique chunk hash, deterministically seed an RNG from
|
|
19
|
+
``SHA-256(hash)`` and sample ``chunk_size`` token IDs from the safe vocab.
|
|
20
|
+
The same hash always produces the same token sequence.
|
|
21
|
+
3. Per request: concatenate the token sequences for each full chunk, add
|
|
22
|
+
``tail_len = seq_len mod chunk_size`` tail tokens (unique per request so
|
|
23
|
+
they are never accidentally cached), decode the whole list to text, and
|
|
24
|
+
write ``{"prompt": <text>, "output_tokens": <N>}``.
|
|
25
|
+
|
|
26
|
+
Usage (module mode)::
|
|
27
|
+
|
|
28
|
+
python3 -m lmcache.tools.cache_simulator.gen_bench_dataset \\
|
|
29
|
+
-i /path/to/lookup_hashes/ \\
|
|
30
|
+
--tokenizer /models/DeepSeek-V3 \\
|
|
31
|
+
--output-len 128 \\
|
|
32
|
+
-o bench_dataset.jsonl
|
|
33
|
+
|
|
34
|
+
Usage (CLI)::
|
|
35
|
+
|
|
36
|
+
lmcache tool cache-simulator gen-dataset \\
|
|
37
|
+
-i /path/to/lookup_hashes/ \\
|
|
38
|
+
--tokenizer /models/DeepSeek-V3 \\
|
|
39
|
+
--output-len 128 \\
|
|
40
|
+
-o bench_dataset.jsonl
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Standard
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
from typing import Any
|
|
46
|
+
import argparse
|
|
47
|
+
import hashlib
|
|
48
|
+
import json
|
|
49
|
+
import random
|
|
50
|
+
import sys
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Safe-vocabulary helpers
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_safe_vocab(tokenizer: Any) -> list[int]:
|
|
58
|
+
"""Return token IDs that round-trip stably through the tokenizer.
|
|
59
|
+
|
|
60
|
+
A *safe* token is one where::
|
|
61
|
+
|
|
62
|
+
tokenizer.encode(tokenizer.decode([id]), add_special_tokens=False) == [id]
|
|
63
|
+
|
|
64
|
+
These tokens can be concatenated safely — decoding a list of safe tokens
|
|
65
|
+
and re-tokenizing it will reproduce the same token IDs as long as each
|
|
66
|
+
token starts with a leading space (preventing BPE merges at boundaries).
|
|
67
|
+
|
|
68
|
+
Tokens that begin with a space (``Ġ``, ``▁``, or ``" "``) are placed
|
|
69
|
+
first so that ``hash_to_tokens`` preferentially picks space-prefixed
|
|
70
|
+
tokens, making BPE boundary merges much less likely.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
tokenizer: A HuggingFace ``PreTrainedTokenizer`` or
|
|
74
|
+
``PreTrainedTokenizerFast`` instance.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A non-empty list of safe token IDs. Raises ``RuntimeError`` if
|
|
78
|
+
fewer than 10 safe tokens are found (the tokenizer is probably
|
|
79
|
+
unsupported).
|
|
80
|
+
"""
|
|
81
|
+
vocab_size = tokenizer.vocab_size
|
|
82
|
+
safe_with_space: list[int] = []
|
|
83
|
+
safe_other: list[int] = []
|
|
84
|
+
|
|
85
|
+
for token_id in range(vocab_size):
|
|
86
|
+
try:
|
|
87
|
+
decoded = tokenizer.decode(
|
|
88
|
+
[token_id], skip_special_tokens=True, clean_up_tokenization_spaces=False
|
|
89
|
+
)
|
|
90
|
+
except Exception:
|
|
91
|
+
continue
|
|
92
|
+
if not decoded or not decoded.isprintable():
|
|
93
|
+
continue
|
|
94
|
+
# Re-encode and check round-trip
|
|
95
|
+
try:
|
|
96
|
+
re_encoded = tokenizer.encode(decoded, add_special_tokens=False)
|
|
97
|
+
except Exception:
|
|
98
|
+
continue
|
|
99
|
+
if re_encoded != [token_id]:
|
|
100
|
+
continue
|
|
101
|
+
# Prefer tokens that start with whitespace (prevents inter-chunk merges)
|
|
102
|
+
if decoded[0] in (" ", "\u0120", "\u2581"):
|
|
103
|
+
safe_with_space.append(token_id)
|
|
104
|
+
else:
|
|
105
|
+
safe_other.append(token_id)
|
|
106
|
+
|
|
107
|
+
result = safe_with_space + safe_other
|
|
108
|
+
if len(result) < 10:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
f"Only {len(result)} safe tokens found in the vocabulary. "
|
|
111
|
+
"This tokenizer may not be supported."
|
|
112
|
+
)
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def hash_to_tokens(chunk_hash: str, n_tokens: int, vocab_ids: list[int]) -> list[int]:
|
|
117
|
+
"""Deterministically map *chunk_hash* to *n_tokens* token IDs.
|
|
118
|
+
|
|
119
|
+
The mapping is a pure function of ``chunk_hash`` and ``n_tokens`` — the
|
|
120
|
+
same inputs always produce the same output regardless of call order.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
chunk_hash: Hex string identifying the chunk (e.g. ``"0xabcd1234"``).
|
|
124
|
+
n_tokens: Number of token IDs to generate.
|
|
125
|
+
vocab_ids: Pool of safe token IDs to sample from.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A list of ``n_tokens`` token IDs drawn from ``vocab_ids``.
|
|
129
|
+
"""
|
|
130
|
+
seed_bytes = hashlib.sha256(chunk_hash.encode()).digest()
|
|
131
|
+
seed = int.from_bytes(seed_bytes, "big") % (2**31)
|
|
132
|
+
rng = random.Random(seed)
|
|
133
|
+
return [rng.choice(vocab_ids) for _ in range(n_tokens)]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Main conversion
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def gen_bench_dataset(
|
|
142
|
+
events: list[dict[str, Any]],
|
|
143
|
+
tokenizer: Any,
|
|
144
|
+
output_len: int,
|
|
145
|
+
output_path: Path,
|
|
146
|
+
) -> int:
|
|
147
|
+
"""Convert lookup-hash events into a vllm bench serve custom dataset.
|
|
148
|
+
|
|
149
|
+
Each event becomes one JSONL line with fields ``"prompt"`` (text) and
|
|
150
|
+
``"output_tokens"`` (int). Chunk hashes are deterministically mapped to
|
|
151
|
+
token sequences so that prefix-sharing in the original workload is
|
|
152
|
+
reproduced in the synthetic prompts.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
events: Lookup-hash events as returned by
|
|
156
|
+
:func:`~lmcache.tools.cache_simulator.simulator.load_lookup_events`.
|
|
157
|
+
tokenizer: A HuggingFace tokenizer loaded for the target model.
|
|
158
|
+
output_len: Number of output tokens for every request in the dataset.
|
|
159
|
+
output_path: Where to write the output JSONL file.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Number of records written.
|
|
163
|
+
"""
|
|
164
|
+
print("Building safe vocabulary …", flush=True)
|
|
165
|
+
vocab_ids = build_safe_vocab(tokenizer)
|
|
166
|
+
space_chars = (" ", "\u0120", "\u2581")
|
|
167
|
+
n_space = sum(1 for t in vocab_ids if tokenizer.decode([t])[0] in space_chars)
|
|
168
|
+
print(
|
|
169
|
+
f" Safe vocab size: {len(vocab_ids)} tokens ({n_space} space-prefixed)",
|
|
170
|
+
flush=True,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Cache: chunk_hash -> token_id list (memoized across requests)
|
|
174
|
+
hash_cache: dict[str, list[int]] = {}
|
|
175
|
+
n_written = 0
|
|
176
|
+
|
|
177
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
with open(output_path, "w", encoding="utf-8") as fout:
|
|
179
|
+
for event in events:
|
|
180
|
+
chunk_hashes: list[str] = event.get("chunk_hashes", [])
|
|
181
|
+
chunk_size: int = event["chunk_size"]
|
|
182
|
+
seq_len: int = event["seq_len"]
|
|
183
|
+
|
|
184
|
+
# --- Full-chunk tokens ----------------------------------------
|
|
185
|
+
all_token_ids: list[int] = []
|
|
186
|
+
for h in chunk_hashes:
|
|
187
|
+
if h not in hash_cache:
|
|
188
|
+
hash_cache[h] = hash_to_tokens(h, chunk_size, vocab_ids)
|
|
189
|
+
all_token_ids.extend(hash_cache[h])
|
|
190
|
+
|
|
191
|
+
# --- Tail tokens ----------------------------------------------
|
|
192
|
+
# seq_len mod chunk_size tail tokens; always a miss in LMCache.
|
|
193
|
+
# Use a per-request unique seed so tails are never accidentally
|
|
194
|
+
# shared across requests.
|
|
195
|
+
tail_len = seq_len - len(chunk_hashes) * chunk_size
|
|
196
|
+
if tail_len > 0:
|
|
197
|
+
# Build a per-request identifier: prefer request_id, fall back
|
|
198
|
+
# to timestamp+index to ensure uniqueness.
|
|
199
|
+
req_id = event.get(
|
|
200
|
+
"request_id", f"{event.get('timestamp', n_written)}_{n_written}"
|
|
201
|
+
)
|
|
202
|
+
tail_hash = f"__tail__{req_id}"
|
|
203
|
+
all_token_ids.extend(hash_to_tokens(tail_hash, tail_len, vocab_ids))
|
|
204
|
+
|
|
205
|
+
# --- Decode and write -----------------------------------------
|
|
206
|
+
prompt_text = tokenizer.decode(
|
|
207
|
+
all_token_ids,
|
|
208
|
+
skip_special_tokens=True,
|
|
209
|
+
clean_up_tokenization_spaces=False,
|
|
210
|
+
)
|
|
211
|
+
record = {"prompt": prompt_text, "output_tokens": output_len}
|
|
212
|
+
fout.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
213
|
+
n_written += 1
|
|
214
|
+
|
|
215
|
+
if n_written % 1000 == 0:
|
|
216
|
+
print(f" {n_written} / {len(events)} records written …", flush=True)
|
|
217
|
+
|
|
218
|
+
return n_written
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# CLI helpers (shared by main() and lmcache tool cache-simulator gen-dataset)
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def add_gen_dataset_arguments(parser: argparse.ArgumentParser) -> None:
|
|
227
|
+
"""Register all gen-dataset CLI flags onto *parser*.
|
|
228
|
+
|
|
229
|
+
Called by both :func:`main` (``python -m`` entry point) and the
|
|
230
|
+
``lmcache tool cache-simulator gen-dataset`` sub-command in
|
|
231
|
+
``lmcache/cli/commands/tool/cache_simulator.py``.
|
|
232
|
+
|
|
233
|
+
**When adding or removing a flag**, edit only this function — the
|
|
234
|
+
``lmcache tool`` command picks up the change automatically.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
parser: The :class:`argparse.ArgumentParser` (or sub-parser) to
|
|
238
|
+
populate.
|
|
239
|
+
"""
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"-i",
|
|
242
|
+
"--input",
|
|
243
|
+
nargs="+",
|
|
244
|
+
required=True,
|
|
245
|
+
metavar="PATH",
|
|
246
|
+
type=Path,
|
|
247
|
+
help=(
|
|
248
|
+
"One or more JSONL files or directories containing "
|
|
249
|
+
"``lookup_hashes_*.jsonl`` files."
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"--tokenizer",
|
|
254
|
+
required=True,
|
|
255
|
+
metavar="PATH",
|
|
256
|
+
help=(
|
|
257
|
+
"Path or HuggingFace model name for the tokenizer used to decode "
|
|
258
|
+
"synthetic token IDs into text. Should match the model that will "
|
|
259
|
+
"be benchmarked."
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
parser.add_argument(
|
|
263
|
+
"--output-len",
|
|
264
|
+
type=int,
|
|
265
|
+
default=128,
|
|
266
|
+
metavar="N",
|
|
267
|
+
help="Number of output tokens for every request (default: %(default)s).",
|
|
268
|
+
)
|
|
269
|
+
parser.add_argument(
|
|
270
|
+
"-o",
|
|
271
|
+
"--output",
|
|
272
|
+
default="bench_dataset.jsonl",
|
|
273
|
+
metavar="FILE",
|
|
274
|
+
help="Output JSONL file path (default: %(default)s).",
|
|
275
|
+
)
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"-n",
|
|
278
|
+
"--max-samples",
|
|
279
|
+
type=int,
|
|
280
|
+
default=None,
|
|
281
|
+
metavar="N",
|
|
282
|
+
help="Truncate to the first N events after sorting by timestamp.",
|
|
283
|
+
)
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
"--model",
|
|
286
|
+
default=None,
|
|
287
|
+
metavar="NAME",
|
|
288
|
+
help=(
|
|
289
|
+
"Filter events by ``model_name`` (exact match). Useful when logs "
|
|
290
|
+
"contain traffic for multiple models."
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def run_gen_dataset(args: argparse.Namespace) -> None:
|
|
296
|
+
"""Execute the gen-dataset workflow from a parsed argument namespace.
|
|
297
|
+
|
|
298
|
+
Loads events via
|
|
299
|
+
:func:`~lmcache.tools.cache_simulator.simulator.load_lookup_events`,
|
|
300
|
+
loads the tokenizer, calls :func:`gen_bench_dataset`, and prints a
|
|
301
|
+
summary.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
args: Parsed CLI namespace produced by a parser that was populated
|
|
305
|
+
with :func:`add_gen_dataset_arguments`.
|
|
306
|
+
"""
|
|
307
|
+
# Lazy imports — keeps CLI startup fast
|
|
308
|
+
# Third Party
|
|
309
|
+
from transformers import AutoTokenizer
|
|
310
|
+
|
|
311
|
+
# First Party
|
|
312
|
+
from lmcache.tools.cache_simulator.simulator import load_lookup_events
|
|
313
|
+
|
|
314
|
+
# ---- Load events -------------------------------------------------------
|
|
315
|
+
print(f"Loading events from: {[str(p) for p in args.input]}", flush=True)
|
|
316
|
+
events = load_lookup_events(
|
|
317
|
+
args.input,
|
|
318
|
+
model=args.model,
|
|
319
|
+
max_samples=args.max_samples,
|
|
320
|
+
)
|
|
321
|
+
if not events:
|
|
322
|
+
print("No events found — nothing to write.", file=sys.stderr)
|
|
323
|
+
sys.exit(1)
|
|
324
|
+
print(f"Loaded {len(events):,} events.", flush=True)
|
|
325
|
+
|
|
326
|
+
# ---- Load tokenizer ----------------------------------------------------
|
|
327
|
+
print(f"Loading tokenizer: {args.tokenizer}", flush=True)
|
|
328
|
+
tokenizer = AutoTokenizer.from_pretrained(args.tokenizer, trust_remote_code=True)
|
|
329
|
+
|
|
330
|
+
# ---- Convert -----------------------------------------------------------
|
|
331
|
+
output_path = Path(args.output)
|
|
332
|
+
n = gen_bench_dataset(
|
|
333
|
+
events=events,
|
|
334
|
+
tokenizer=tokenizer,
|
|
335
|
+
output_len=args.output_len,
|
|
336
|
+
output_path=output_path,
|
|
337
|
+
)
|
|
338
|
+
print(f"\nWrote {n:,} records to {output_path}")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
# Module entry point
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main() -> None:
|
|
347
|
+
"""CLI entry point for ``python -m``."""
|
|
348
|
+
parser = argparse.ArgumentParser(
|
|
349
|
+
description=(
|
|
350
|
+
"Convert LMCache lookup-hash JSONL logs into a "
|
|
351
|
+
"vllm bench serve custom dataset."
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
add_gen_dataset_arguments(parser)
|
|
355
|
+
args = parser.parse_args()
|
|
356
|
+
run_gen_dataset(args)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
if __name__ == "__main__":
|
|
360
|
+
main()
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
LRU cache implementations for the cache simulator.
|
|
4
|
+
|
|
5
|
+
Two variants are provided:
|
|
6
|
+
|
|
7
|
+
* ``LRUCacheFast`` — O(1) OrderedDict-backed. Supports only hit/miss queries.
|
|
8
|
+
Use this for capacity sweeps where per-chunk statistics are not needed.
|
|
9
|
+
|
|
10
|
+
* ``LRUCache`` — O(log n) dict + SortedList. Adds ``position(key)`` for
|
|
11
|
+
computing cache-position statistics (0 = MRU, capacity-1 = LRU).
|
|
12
|
+
Use this for the detailed single-run report.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Standard
|
|
16
|
+
from collections import OrderedDict
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
# Third Party
|
|
20
|
+
from sortedcontainers import SortedList
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LRUCacheFast:
|
|
24
|
+
"""
|
|
25
|
+
Lightweight LRU cache using a single :class:`OrderedDict`.
|
|
26
|
+
|
|
27
|
+
All operations are O(1). No per-key position tracking.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
capacity:
|
|
32
|
+
Maximum number of entries before LRU eviction kicks in.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, capacity: int) -> None:
|
|
36
|
+
if capacity < 1:
|
|
37
|
+
raise ValueError(f"LRUCacheFast capacity must be >= 1, got {capacity}")
|
|
38
|
+
self.capacity = capacity
|
|
39
|
+
self._cache: OrderedDict[Any, None] = OrderedDict()
|
|
40
|
+
self.eviction_count: int = 0
|
|
41
|
+
|
|
42
|
+
def contains(self, key: Any) -> bool:
|
|
43
|
+
return key in self._cache
|
|
44
|
+
|
|
45
|
+
def access(self, key: Any) -> None:
|
|
46
|
+
"""Mark an existing entry as most-recently used. O(1)."""
|
|
47
|
+
self._cache.move_to_end(key)
|
|
48
|
+
|
|
49
|
+
def insert(self, key: Any) -> None:
|
|
50
|
+
"""Insert a new entry, or refresh if already present. O(1)."""
|
|
51
|
+
if key in self._cache:
|
|
52
|
+
self._cache.move_to_end(key)
|
|
53
|
+
return
|
|
54
|
+
if len(self._cache) >= self.capacity:
|
|
55
|
+
self._cache.popitem(last=False)
|
|
56
|
+
self.eviction_count += 1
|
|
57
|
+
self._cache[key] = None
|
|
58
|
+
|
|
59
|
+
def __len__(self) -> int:
|
|
60
|
+
return len(self._cache)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class LRUCache:
|
|
64
|
+
"""
|
|
65
|
+
LRU cache backed by a dict (O(1) lookup) and a
|
|
66
|
+
:class:`~sortedcontainers.SortedList` (O(log n) rank queries).
|
|
67
|
+
|
|
68
|
+
Each entry carries a strictly-increasing clock value so that the SortedList
|
|
69
|
+
order is unambiguous.
|
|
70
|
+
|
|
71
|
+
* LRU end = smallest clock value = ``SortedList[0]``
|
|
72
|
+
* MRU end = largest clock value = ``SortedList[-1]``
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
capacity:
|
|
77
|
+
Maximum number of entries before LRU eviction kicks in.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, capacity: int) -> None:
|
|
81
|
+
if capacity < 1:
|
|
82
|
+
raise ValueError(f"LRUCache capacity must be >= 1, got {capacity}")
|
|
83
|
+
self.capacity = capacity
|
|
84
|
+
self._clock: int = 0
|
|
85
|
+
self._map: dict[Any, int] = {} # key -> clock value at last access
|
|
86
|
+
self._sl: SortedList = SortedList(key=lambda x: x[0])
|
|
87
|
+
self.eviction_count: int = 0
|
|
88
|
+
|
|
89
|
+
def contains(self, key: Any) -> bool:
|
|
90
|
+
return key in self._map
|
|
91
|
+
|
|
92
|
+
def position(self, key: Any) -> int:
|
|
93
|
+
"""
|
|
94
|
+
LRU rank of *key*: 0 = most-recently used, ``len-1`` = least-recently used.
|
|
95
|
+
O(log n).
|
|
96
|
+
"""
|
|
97
|
+
clock = self._map[key]
|
|
98
|
+
idx = self._sl.index((clock, key))
|
|
99
|
+
return len(self._sl) - 1 - idx
|
|
100
|
+
|
|
101
|
+
def access(self, key: Any) -> None:
|
|
102
|
+
"""Mark an existing entry as most-recently used. O(log n)."""
|
|
103
|
+
old_clock = self._map[key]
|
|
104
|
+
self._sl.remove((old_clock, key))
|
|
105
|
+
self._clock += 1
|
|
106
|
+
self._map[key] = self._clock
|
|
107
|
+
self._sl.add((self._clock, key))
|
|
108
|
+
|
|
109
|
+
def insert(self, key: Any) -> None:
|
|
110
|
+
"""Insert a new entry, or refresh if already present. O(log n)."""
|
|
111
|
+
if key in self._map:
|
|
112
|
+
self.access(key)
|
|
113
|
+
return
|
|
114
|
+
if len(self._map) >= self.capacity:
|
|
115
|
+
lru_clock, lru_key = self._sl[0]
|
|
116
|
+
self._sl.remove((lru_clock, lru_key))
|
|
117
|
+
del self._map[lru_key]
|
|
118
|
+
self.eviction_count += 1
|
|
119
|
+
self._clock += 1
|
|
120
|
+
self._map[key] = self._clock
|
|
121
|
+
self._sl.add((self._clock, key))
|
|
122
|
+
|
|
123
|
+
def __len__(self) -> int:
|
|
124
|
+
return len(self._map)
|