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,795 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Cache hit-rate simulator driven by LMCache lookup-hash JSONL logs.
|
|
4
|
+
|
|
5
|
+
The simulator replays ``MP_LOOKUP`` events recorded by
|
|
6
|
+
:class:`~lmcache.v1.mp_observability.subscribers.logging.lookup_hash.LookupHashLoggingSubscriber`.
|
|
7
|
+
Each event contains the ordered list of *full-chunk* hashes that were looked up
|
|
8
|
+
for a single request, together with the sequence length and chunk size.
|
|
9
|
+
|
|
10
|
+
**Token cache hit rate** (the primary metric) is defined as::
|
|
11
|
+
|
|
12
|
+
token_hit_rate = total_hit_tokens / total_tokens
|
|
13
|
+
|
|
14
|
+
where:
|
|
15
|
+
|
|
16
|
+
* ``total_tokens`` = sum of ``seq_len`` across all requests (includes tail tokens
|
|
17
|
+
that do not fill a complete chunk — these are *always* a miss because LMCache
|
|
18
|
+
only caches complete chunks).
|
|
19
|
+
* ``total_hit_tokens`` = number of tokens covered by a *continuous prefix* of
|
|
20
|
+
cache-hit chunks at the start of each request, i.e.
|
|
21
|
+
``hit_prefix_chunks × chunk_size``.
|
|
22
|
+
|
|
23
|
+
Running the simulator prints a text report **and** saves a multi-panel PNG with
|
|
24
|
+
seven statistical charts.
|
|
25
|
+
|
|
26
|
+
Usage (module mode)::
|
|
27
|
+
|
|
28
|
+
python3 -m lmcache.tools.cache_simulator.simulator \\
|
|
29
|
+
-i /path/to/lookup_hashes/ \\
|
|
30
|
+
--cache-capacity-gib 64 \\
|
|
31
|
+
-o stats.png
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Standard
|
|
35
|
+
from collections import defaultdict
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any
|
|
38
|
+
import argparse
|
|
39
|
+
import json
|
|
40
|
+
import math
|
|
41
|
+
import sys
|
|
42
|
+
import warnings
|
|
43
|
+
|
|
44
|
+
# First Party
|
|
45
|
+
from lmcache.tools.cache_simulator.lru_cache import LRUCache, LRUCacheFast
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Dtype → bytes mapping
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
_DTYPE_BYTES: dict[str, int] = {
|
|
52
|
+
"float32": 4,
|
|
53
|
+
"float16": 2,
|
|
54
|
+
"bfloat16": 2,
|
|
55
|
+
"float8_e4m3fn": 1,
|
|
56
|
+
"float8_e5m2": 1,
|
|
57
|
+
"int8": 1,
|
|
58
|
+
"int32": 4,
|
|
59
|
+
"int64": 8,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_GIB = 2**30
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Public helpers
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def compute_kv_bytes_per_chunk(event: dict[str, Any]) -> int:
|
|
71
|
+
"""
|
|
72
|
+
Compute the number of KV-cache bytes that one chunk occupies.
|
|
73
|
+
|
|
74
|
+
The value is derived from the ``shapes`` and ``dtypes`` fields of a single
|
|
75
|
+
lookup event. Each ``(shape, dtype)`` pair represents one tensor stored
|
|
76
|
+
per chunk (e.g. key and value tensors for all layers); their byte sizes are
|
|
77
|
+
summed.
|
|
78
|
+
|
|
79
|
+
Returns 0 if ``shapes`` or ``dtypes`` is empty (caller must handle this).
|
|
80
|
+
"""
|
|
81
|
+
shapes = event.get("shapes", [])
|
|
82
|
+
dtypes = event.get("dtypes", [])
|
|
83
|
+
if not shapes or not dtypes:
|
|
84
|
+
return 0
|
|
85
|
+
total = 0
|
|
86
|
+
for shape, dt in zip(shapes, dtypes, strict=False):
|
|
87
|
+
elem_bytes = _DTYPE_BYTES.get(dt, 0)
|
|
88
|
+
if elem_bytes == 0:
|
|
89
|
+
warnings.warn(
|
|
90
|
+
f"Unknown dtype '{dt}' — treating as 0 bytes per element.",
|
|
91
|
+
UserWarning,
|
|
92
|
+
stacklevel=2,
|
|
93
|
+
)
|
|
94
|
+
total += math.prod(shape) * elem_bytes
|
|
95
|
+
return total
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def load_lookup_events(
|
|
99
|
+
paths: list[Path],
|
|
100
|
+
model: str | None = None,
|
|
101
|
+
max_samples: int | None = None,
|
|
102
|
+
) -> list[dict[str, Any]]:
|
|
103
|
+
"""
|
|
104
|
+
Load and return lookup events from one or more JSONL files or directories.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
paths:
|
|
109
|
+
Each element may be a ``.jsonl`` file or a directory. Directories are
|
|
110
|
+
globbed for ``lookup_hashes_*.jsonl`` files.
|
|
111
|
+
model:
|
|
112
|
+
If given, only events whose ``model_name`` exactly matches this string
|
|
113
|
+
are returned.
|
|
114
|
+
max_samples:
|
|
115
|
+
If given, truncate the final sorted list to this many events.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
list[dict]
|
|
120
|
+
Events sorted by ``timestamp`` ascending.
|
|
121
|
+
"""
|
|
122
|
+
all_events: list[dict[str, Any]] = []
|
|
123
|
+
|
|
124
|
+
for p in paths:
|
|
125
|
+
files: list[Path]
|
|
126
|
+
if p.is_dir():
|
|
127
|
+
files = sorted(p.glob("lookup_hashes_*.jsonl"))
|
|
128
|
+
if not files:
|
|
129
|
+
warnings.warn(
|
|
130
|
+
f"Directory '{p}' contains no lookup_hashes_*.jsonl files.",
|
|
131
|
+
UserWarning,
|
|
132
|
+
stacklevel=2,
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
files = [p]
|
|
136
|
+
|
|
137
|
+
for f in files:
|
|
138
|
+
try:
|
|
139
|
+
with open(f, encoding="utf-8") as fh:
|
|
140
|
+
for lineno, line in enumerate(fh, start=1):
|
|
141
|
+
line = line.strip()
|
|
142
|
+
if not line:
|
|
143
|
+
continue
|
|
144
|
+
try:
|
|
145
|
+
event = json.loads(line)
|
|
146
|
+
except json.JSONDecodeError as exc:
|
|
147
|
+
warnings.warn(
|
|
148
|
+
f"{f}:{lineno}: skipping malformed JSON — {exc}",
|
|
149
|
+
UserWarning,
|
|
150
|
+
stacklevel=2,
|
|
151
|
+
)
|
|
152
|
+
continue
|
|
153
|
+
if model is not None and event.get("model_name") != model:
|
|
154
|
+
continue
|
|
155
|
+
all_events.append(event)
|
|
156
|
+
except OSError as exc:
|
|
157
|
+
warnings.warn(
|
|
158
|
+
f"Could not open '{f}': {exc}",
|
|
159
|
+
UserWarning,
|
|
160
|
+
stacklevel=2,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
all_events.sort(key=lambda e: e.get("timestamp", 0.0))
|
|
164
|
+
|
|
165
|
+
if max_samples is not None and max_samples > 0:
|
|
166
|
+
all_events = all_events[:max_samples]
|
|
167
|
+
|
|
168
|
+
return all_events
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
# Simulation
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def simulate(
|
|
177
|
+
events: list[dict[str, Any]],
|
|
178
|
+
cache_capacity_bytes: int,
|
|
179
|
+
kv_bytes_per_chunk: int,
|
|
180
|
+
fast: bool = False,
|
|
181
|
+
) -> dict[str, Any]:
|
|
182
|
+
"""
|
|
183
|
+
Replay lookup events through an LRU cache and compute token hit-rate
|
|
184
|
+
statistics.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
events:
|
|
189
|
+
Lookup events as returned by :func:`load_lookup_events`.
|
|
190
|
+
cache_capacity_bytes:
|
|
191
|
+
Total cache capacity in bytes.
|
|
192
|
+
kv_bytes_per_chunk:
|
|
193
|
+
Bytes consumed by one cached chunk.
|
|
194
|
+
fast:
|
|
195
|
+
If ``True``, use :class:`~lmcache.tools.cache_simulator.lru_cache.LRUCacheFast`
|
|
196
|
+
and skip per-chunk statistics (faster for capacity sweeps).
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
dict
|
|
201
|
+
Simulation results (see source for field list).
|
|
202
|
+
"""
|
|
203
|
+
if kv_bytes_per_chunk <= 0:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
"kv_bytes_per_chunk must be > 0. "
|
|
206
|
+
"Either pass --kv-bytes-per-chunk or ensure the JSONL records "
|
|
207
|
+
"contain non-empty 'shapes' and 'dtypes' fields."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
cache_capacity_chunks = max(1, cache_capacity_bytes // kv_bytes_per_chunk)
|
|
211
|
+
|
|
212
|
+
cache: LRUCacheFast | LRUCache
|
|
213
|
+
if fast:
|
|
214
|
+
cache = LRUCacheFast(cache_capacity_chunks)
|
|
215
|
+
else:
|
|
216
|
+
cache = LRUCache(cache_capacity_chunks)
|
|
217
|
+
|
|
218
|
+
# ── Aggregates ──────────────────────────────────────────────────────────
|
|
219
|
+
total_requests = 0
|
|
220
|
+
total_tokens = 0
|
|
221
|
+
total_hit_tokens = 0
|
|
222
|
+
|
|
223
|
+
# ── Per-request (skipped in fast mode) ──────────────────────────────────
|
|
224
|
+
per_request_token_hit_rates: list[float] = []
|
|
225
|
+
hit_prefix_lengths: list[int] = []
|
|
226
|
+
rolling_token_hit_rate: list[float] = []
|
|
227
|
+
input_lengths: list[int] = []
|
|
228
|
+
|
|
229
|
+
# ── Chunk-level (skipped in fast mode) ──────────────────────────────────
|
|
230
|
+
chunk_reuse_counts: dict[str, int] = defaultdict(int)
|
|
231
|
+
chunk_last_seen: dict[str, int] = {}
|
|
232
|
+
global_span_distribution: list[int] = []
|
|
233
|
+
cache_position_distribution: list[int] = []
|
|
234
|
+
global_chunk_index = 0
|
|
235
|
+
|
|
236
|
+
for event in events:
|
|
237
|
+
hashes: list[str] = event.get("chunk_hashes", [])
|
|
238
|
+
seq_len: int = event.get("seq_len", 0)
|
|
239
|
+
chunk_sz: int = event.get("chunk_size", 1)
|
|
240
|
+
|
|
241
|
+
if not hashes and seq_len == 0:
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
# ── Prefix hit count ────────────────────────────────────────────────
|
|
245
|
+
hit_prefix = 0
|
|
246
|
+
for h in hashes:
|
|
247
|
+
if cache.contains(h):
|
|
248
|
+
hit_prefix += 1
|
|
249
|
+
else:
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
# ── Token accounting ────────────────────────────────────────────────
|
|
253
|
+
# Tail tokens (seq_len - len(hashes)*chunk_sz) are always a miss.
|
|
254
|
+
hit_tokens = hit_prefix * chunk_sz
|
|
255
|
+
request_tokens = seq_len # includes tail tokens
|
|
256
|
+
|
|
257
|
+
total_requests += 1
|
|
258
|
+
total_tokens += request_tokens
|
|
259
|
+
total_hit_tokens += hit_tokens
|
|
260
|
+
|
|
261
|
+
if not fast:
|
|
262
|
+
input_lengths.append(seq_len)
|
|
263
|
+
per_request_token_hit_rates.append(
|
|
264
|
+
hit_tokens / request_tokens if request_tokens > 0 else 0.0
|
|
265
|
+
)
|
|
266
|
+
hit_prefix_lengths.append(hit_prefix)
|
|
267
|
+
rolling_token_hit_rate.append(
|
|
268
|
+
total_hit_tokens / total_tokens if total_tokens > 0 else 0.0
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Per-hit-chunk statistics
|
|
272
|
+
for i, h in enumerate(hashes[:hit_prefix]):
|
|
273
|
+
chunk_reuse_counts[h] += 1
|
|
274
|
+
if h in chunk_last_seen:
|
|
275
|
+
global_span_distribution.append(
|
|
276
|
+
global_chunk_index + i - chunk_last_seen[h]
|
|
277
|
+
)
|
|
278
|
+
if isinstance(cache, LRUCache):
|
|
279
|
+
cache_position_distribution.append(cache.position(h))
|
|
280
|
+
|
|
281
|
+
# ── Update cache ────────────────────────────────────────────────────
|
|
282
|
+
for i, h in enumerate(hashes):
|
|
283
|
+
if not fast:
|
|
284
|
+
chunk_last_seen[h] = global_chunk_index + i
|
|
285
|
+
if i < hit_prefix:
|
|
286
|
+
cache.access(h)
|
|
287
|
+
else:
|
|
288
|
+
cache.insert(h)
|
|
289
|
+
|
|
290
|
+
if not fast:
|
|
291
|
+
global_chunk_index += len(hashes)
|
|
292
|
+
|
|
293
|
+
token_hit_rate = total_hit_tokens / total_tokens if total_tokens > 0 else 0.0
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
# ── Aggregates ──────────────────────────────────────────────────────
|
|
297
|
+
"total_requests": total_requests,
|
|
298
|
+
"total_tokens": total_tokens,
|
|
299
|
+
"total_hit_tokens": total_hit_tokens,
|
|
300
|
+
"total_miss_tokens": total_tokens - total_hit_tokens,
|
|
301
|
+
"token_hit_rate": token_hit_rate,
|
|
302
|
+
"eviction_count": cache.eviction_count,
|
|
303
|
+
"cache_size_at_end_chunks": len(cache),
|
|
304
|
+
"cache_capacity_chunks": cache_capacity_chunks,
|
|
305
|
+
"cache_capacity_bytes": cache_capacity_bytes,
|
|
306
|
+
"kv_bytes_per_chunk": kv_bytes_per_chunk,
|
|
307
|
+
# ── Per-request ─────────────────────────────────────────────────────
|
|
308
|
+
"per_request_token_hit_rates": per_request_token_hit_rates,
|
|
309
|
+
"hit_prefix_lengths": hit_prefix_lengths,
|
|
310
|
+
"input_lengths": input_lengths,
|
|
311
|
+
"rolling_token_hit_rate": rolling_token_hit_rate,
|
|
312
|
+
# ── Chunk-level ─────────────────────────────────────────────────────
|
|
313
|
+
"chunk_reuse_counts": dict(chunk_reuse_counts),
|
|
314
|
+
"global_span_distribution": global_span_distribution,
|
|
315
|
+
"cache_position_distribution": cache_position_distribution,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# ---------------------------------------------------------------------------
|
|
320
|
+
# Reporting — text
|
|
321
|
+
# ---------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _percentiles(values: list[float], pcts: list[int]) -> dict[str, float]:
|
|
325
|
+
if not values:
|
|
326
|
+
return {}
|
|
327
|
+
s = sorted(values)
|
|
328
|
+
n = len(s)
|
|
329
|
+
result = {}
|
|
330
|
+
for p in pcts:
|
|
331
|
+
idx = min(int(p / 100 * n), n - 1)
|
|
332
|
+
result[f"p{p}"] = s[idx]
|
|
333
|
+
return result
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def print_statistics(results: dict[str, Any]) -> None:
|
|
337
|
+
sep = "=" * 60
|
|
338
|
+
|
|
339
|
+
gib = results["cache_capacity_bytes"] / _GIB
|
|
340
|
+
print(sep)
|
|
341
|
+
print("Aggregate")
|
|
342
|
+
print(sep)
|
|
343
|
+
print(f" Requests processed : {results['total_requests']:,}")
|
|
344
|
+
print(f" Total tokens : {results['total_tokens']:,}")
|
|
345
|
+
print(f" Hit tokens : {results['total_hit_tokens']:,}")
|
|
346
|
+
print(f" Miss tokens : {results['total_miss_tokens']:,}")
|
|
347
|
+
print(f" Token hit rate : {results['token_hit_rate']:.2%}")
|
|
348
|
+
print(
|
|
349
|
+
f" Cache capacity : {gib:.2f} GiB "
|
|
350
|
+
f"({results['cache_capacity_chunks']:,} chunks × "
|
|
351
|
+
f"{results['kv_bytes_per_chunk']:,} bytes/chunk)"
|
|
352
|
+
)
|
|
353
|
+
print(
|
|
354
|
+
f" Cache occupancy : {results['cache_size_at_end_chunks']:,} / "
|
|
355
|
+
f"{results['cache_capacity_chunks']:,} chunks"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
rates = results["per_request_token_hit_rates"]
|
|
359
|
+
if rates:
|
|
360
|
+
zero_hit = sum(1 for r in rates if r == 0.0)
|
|
361
|
+
full_hit = sum(1 for r in rates if r == 1.0)
|
|
362
|
+
pcts = _percentiles(rates, [25, 50, 75, 90, 99])
|
|
363
|
+
print()
|
|
364
|
+
print(sep)
|
|
365
|
+
print("Stat 1 — Per-request token hit rate distribution")
|
|
366
|
+
print(sep)
|
|
367
|
+
print(
|
|
368
|
+
f" Requests with 0% hit rate : "
|
|
369
|
+
f"{zero_hit:,} ({zero_hit / len(rates):.1%})"
|
|
370
|
+
)
|
|
371
|
+
print(
|
|
372
|
+
f" Requests with 100% hit rate : "
|
|
373
|
+
f"{full_hit:,} ({full_hit / len(rates):.1%})"
|
|
374
|
+
)
|
|
375
|
+
print(f" Mean : {sum(rates) / len(rates):.2%}")
|
|
376
|
+
for k, v in pcts.items():
|
|
377
|
+
print(f" {k:4s} : {v:.2%}")
|
|
378
|
+
|
|
379
|
+
lengths = results["hit_prefix_lengths"]
|
|
380
|
+
if lengths:
|
|
381
|
+
pcts_len = _percentiles([float(x) for x in lengths], [25, 50, 75, 90, 99])
|
|
382
|
+
print()
|
|
383
|
+
print(sep)
|
|
384
|
+
print("Stat 2 — Hit prefix length per request (chunks)")
|
|
385
|
+
print(sep)
|
|
386
|
+
print(f" Mean : {sum(lengths) / len(lengths):.1f}")
|
|
387
|
+
for k, v in pcts_len.items():
|
|
388
|
+
print(f" {k:4s} : {v:.0f}")
|
|
389
|
+
|
|
390
|
+
reuse = sorted(results["chunk_reuse_counts"].values())
|
|
391
|
+
if reuse:
|
|
392
|
+
pcts_reuse = _percentiles([float(x) for x in reuse], [25, 50, 75, 90, 99])
|
|
393
|
+
print()
|
|
394
|
+
print(sep)
|
|
395
|
+
print("Stat 3 — Chunk reuse count distribution")
|
|
396
|
+
print(sep)
|
|
397
|
+
print(f" Unique chunks hit at least once : {len(reuse):,}")
|
|
398
|
+
print(f" Mean reuse count : {sum(reuse) / len(reuse):.1f}")
|
|
399
|
+
print(f" Max reuse count : {reuse[-1]:,}")
|
|
400
|
+
for k, v in pcts_reuse.items():
|
|
401
|
+
print(f" {k:4s} : {v:.0f}")
|
|
402
|
+
|
|
403
|
+
rolling = results["rolling_token_hit_rate"]
|
|
404
|
+
if rolling:
|
|
405
|
+
print()
|
|
406
|
+
print(sep)
|
|
407
|
+
print("Stat 4 — Rolling (cumulative) token hit rate over time")
|
|
408
|
+
print(sep)
|
|
409
|
+
n = len(rolling)
|
|
410
|
+
for frac in (0.1, 0.25, 0.5, 0.75, 1.0):
|
|
411
|
+
idx = max(0, min(int(n * frac) - 1, n - 1))
|
|
412
|
+
print(f" After request {idx + 1:>6,} : {rolling[idx]:.2%}")
|
|
413
|
+
|
|
414
|
+
print()
|
|
415
|
+
print(sep)
|
|
416
|
+
print("Stat 5 — Evictions")
|
|
417
|
+
print(sep)
|
|
418
|
+
print(f" Total evictions : {results['eviction_count']:,}")
|
|
419
|
+
|
|
420
|
+
spans = results["global_span_distribution"]
|
|
421
|
+
if spans:
|
|
422
|
+
pcts_span = _percentiles([float(x) for x in spans], [25, 50, 75, 90, 99])
|
|
423
|
+
print()
|
|
424
|
+
print(sep)
|
|
425
|
+
print("Stat 6 — Global span distribution (chunks between last store and hit)")
|
|
426
|
+
print(sep)
|
|
427
|
+
print(f" Total hit chunks : {len(spans):,}")
|
|
428
|
+
print(f" Mean span : {sum(spans) / len(spans):.1f}")
|
|
429
|
+
print(f" Max span : {max(spans):,}")
|
|
430
|
+
for k, v in pcts_span.items():
|
|
431
|
+
print(f" {k:4s} : {v:.0f}")
|
|
432
|
+
|
|
433
|
+
positions = results["cache_position_distribution"]
|
|
434
|
+
if positions:
|
|
435
|
+
pcts_pos = _percentiles([float(x) for x in positions], [25, 50, 75, 90, 99])
|
|
436
|
+
print()
|
|
437
|
+
print(sep)
|
|
438
|
+
print("Stat 7 — Cache position at hit (0 = MRU, max = LRU)")
|
|
439
|
+
print(sep)
|
|
440
|
+
print(f" Mean position : {sum(positions) / len(positions):.1f}")
|
|
441
|
+
print(f" Max position : {max(positions):,}")
|
|
442
|
+
for k, v in pcts_pos.items():
|
|
443
|
+
print(f" {k:4s} : {v:.0f}")
|
|
444
|
+
|
|
445
|
+
print(sep)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# ---------------------------------------------------------------------------
|
|
449
|
+
# Reporting — charts
|
|
450
|
+
# ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def plot_statistics(
|
|
454
|
+
results: dict[str, Any], events: list[dict[str, Any]], output: str
|
|
455
|
+
) -> None:
|
|
456
|
+
"""
|
|
457
|
+
Render and save a 2×4 multi-panel figure with seven statistical charts.
|
|
458
|
+
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
results:
|
|
462
|
+
Output of :func:`simulate` with ``fast=False``.
|
|
463
|
+
events:
|
|
464
|
+
The event list used to produce *results* (used for chunk_size label).
|
|
465
|
+
output:
|
|
466
|
+
Output file path (PNG).
|
|
467
|
+
"""
|
|
468
|
+
cap_gib = results["cache_capacity_bytes"] / _GIB
|
|
469
|
+
chunk_size = events[0].get("chunk_size", "?") if events else "?"
|
|
470
|
+
n_req = results["total_requests"]
|
|
471
|
+
|
|
472
|
+
per_request_hit_rates = [r * 100 for r in results["per_request_token_hit_rates"]]
|
|
473
|
+
hit_prefix_lengths = results["hit_prefix_lengths"]
|
|
474
|
+
reuse_counts = sorted(results["chunk_reuse_counts"].values())
|
|
475
|
+
rolling = [r * 100 for r in results["rolling_token_hit_rate"]]
|
|
476
|
+
input_lengths = results["input_lengths"]
|
|
477
|
+
global_spans = results["global_span_distribution"]
|
|
478
|
+
cache_positions = results["cache_position_distribution"]
|
|
479
|
+
|
|
480
|
+
# Third Party
|
|
481
|
+
import matplotlib.pyplot as plt # noqa: PLC0415 — lazy import to avoid hard dependency
|
|
482
|
+
|
|
483
|
+
fig, axes = plt.subplots(2, 4, figsize=(22, 10))
|
|
484
|
+
fig.suptitle(
|
|
485
|
+
f"Cache simulation statistics "
|
|
486
|
+
f"(chunk_size={chunk_size} tokens, capacity={cap_gib:.1f} GiB, "
|
|
487
|
+
f"{n_req:,} requests, token hit rate={results['token_hit_rate']:.2%})",
|
|
488
|
+
fontsize=12,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# ------------------------------------------------------------------
|
|
492
|
+
# Plot 1 — Per-request token hit rate (non-zero requests only)
|
|
493
|
+
# Two small pies: left = requests hit/miss, right = tokens hit/miss
|
|
494
|
+
# ------------------------------------------------------------------
|
|
495
|
+
ax = axes[0, 0]
|
|
496
|
+
nonzero = [r for r in per_request_hit_rates if r > 0]
|
|
497
|
+
n_zero = len(per_request_hit_rates) - len(nonzero)
|
|
498
|
+
ax.hist(nonzero, bins=50, edgecolor="black", linewidth=0.4)
|
|
499
|
+
ax.set_xlabel("Token hit rate (%) — zero-hit requests excluded")
|
|
500
|
+
ax.set_ylabel("Number of requests")
|
|
501
|
+
ax.set_title("1. Per-request token hit rate")
|
|
502
|
+
|
|
503
|
+
# Left pie — requests
|
|
504
|
+
ax_pie = ax.inset_axes([0.01, 0.52, 0.24, 0.42])
|
|
505
|
+
ax_pie.patch.set_alpha(0)
|
|
506
|
+
wedges, _, _ = ax_pie.pie(
|
|
507
|
+
[len(nonzero), n_zero],
|
|
508
|
+
labels=["hit", "miss"],
|
|
509
|
+
autopct="%1.0f%%",
|
|
510
|
+
startangle=90,
|
|
511
|
+
textprops={"fontsize": 5},
|
|
512
|
+
colors=["#4C72B0", "#DD8452"],
|
|
513
|
+
)
|
|
514
|
+
for w in wedges:
|
|
515
|
+
w.set_alpha(0.6)
|
|
516
|
+
ax_pie.set_title("requests", fontsize=5, pad=2)
|
|
517
|
+
ax_pie.text(
|
|
518
|
+
0.5,
|
|
519
|
+
-0.08,
|
|
520
|
+
"Fraction of requests\nwith ≥1 chunk hit",
|
|
521
|
+
transform=ax_pie.transAxes,
|
|
522
|
+
fontsize=5,
|
|
523
|
+
ha="center",
|
|
524
|
+
va="top",
|
|
525
|
+
color="dimgray",
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Right pie — tokens
|
|
529
|
+
ax_pie2 = ax.inset_axes([0.27, 0.52, 0.24, 0.42])
|
|
530
|
+
ax_pie2.patch.set_alpha(0)
|
|
531
|
+
wedges2, _, _ = ax_pie2.pie(
|
|
532
|
+
[results["total_hit_tokens"], results["total_miss_tokens"]],
|
|
533
|
+
labels=["hit", "miss"],
|
|
534
|
+
autopct="%1.0f%%",
|
|
535
|
+
startangle=90,
|
|
536
|
+
textprops={"fontsize": 5},
|
|
537
|
+
colors=["#4C72B0", "#DD8452"],
|
|
538
|
+
)
|
|
539
|
+
for w in wedges2:
|
|
540
|
+
w.set_alpha(0.6)
|
|
541
|
+
ax_pie2.set_title("tokens", fontsize=5, pad=2)
|
|
542
|
+
ax_pie2.text(
|
|
543
|
+
0.5,
|
|
544
|
+
-0.08,
|
|
545
|
+
"Fraction of tokens\nserved from cache",
|
|
546
|
+
transform=ax_pie2.transAxes,
|
|
547
|
+
fontsize=5,
|
|
548
|
+
ha="center",
|
|
549
|
+
va="top",
|
|
550
|
+
color="dimgray",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# ------------------------------------------------------------------
|
|
554
|
+
# Plot 1b — Zoom into 97–100% hit rate
|
|
555
|
+
# ------------------------------------------------------------------
|
|
556
|
+
ax = axes[0, 1]
|
|
557
|
+
n_full = sum(1 for r in per_request_hit_rates if r == 100)
|
|
558
|
+
high = [r for r in nonzero if r >= 97]
|
|
559
|
+
ax.hist(high, bins=20, edgecolor="black", linewidth=0.4)
|
|
560
|
+
ax.set_xlim(97, 100)
|
|
561
|
+
ax.set_xlabel("Token hit rate (%) — 97–100% zoom")
|
|
562
|
+
ax.set_ylabel("Number of requests")
|
|
563
|
+
ax.set_title("1b. Per-request token hit rate (97–100%)")
|
|
564
|
+
ax.text(
|
|
565
|
+
0.03,
|
|
566
|
+
0.95,
|
|
567
|
+
f"100% hit: {n_full:,} requests",
|
|
568
|
+
transform=ax.transAxes,
|
|
569
|
+
fontsize=8,
|
|
570
|
+
ha="left",
|
|
571
|
+
va="top",
|
|
572
|
+
bbox=dict(boxstyle="round,pad=0.3", facecolor="wheat", alpha=0.7),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# ------------------------------------------------------------------
|
|
576
|
+
# Plot 2 — Hit prefix length per request (clean histogram, no pie)
|
|
577
|
+
# ------------------------------------------------------------------
|
|
578
|
+
ax = axes[0, 2]
|
|
579
|
+
nonzero_prefix = [n for n in hit_prefix_lengths if n > 0]
|
|
580
|
+
ax.hist(nonzero_prefix, bins=50, edgecolor="black", linewidth=0.4)
|
|
581
|
+
ax.set_xlabel("Hit prefix length (chunks) — zero-hit requests excluded")
|
|
582
|
+
ax.set_ylabel("Number of requests")
|
|
583
|
+
ax.set_title("2. Hit prefix length per request")
|
|
584
|
+
|
|
585
|
+
# Plot 3 — Chunk reuse count
|
|
586
|
+
# ------------------------------------------------------------------
|
|
587
|
+
ax = axes[0, 3]
|
|
588
|
+
if reuse_counts:
|
|
589
|
+
cap = min(max(reuse_counts), 100)
|
|
590
|
+
ax.hist(
|
|
591
|
+
[r for r in reuse_counts if r <= cap],
|
|
592
|
+
bins=range(1, cap + 2),
|
|
593
|
+
edgecolor="black",
|
|
594
|
+
linewidth=0.4,
|
|
595
|
+
)
|
|
596
|
+
if max(reuse_counts) > cap:
|
|
597
|
+
n_above = sum(1 for r in reuse_counts if r > cap)
|
|
598
|
+
pct_above = n_above / len(reuse_counts) * 100
|
|
599
|
+
ax.text(
|
|
600
|
+
0.97,
|
|
601
|
+
0.95,
|
|
602
|
+
f"max={max(reuse_counts):,}\n"
|
|
603
|
+
f"{n_above:,} chunks ({pct_above:.1f}%) above cap",
|
|
604
|
+
transform=ax.transAxes,
|
|
605
|
+
fontsize=8,
|
|
606
|
+
ha="right",
|
|
607
|
+
va="top",
|
|
608
|
+
bbox=dict(boxstyle="round,pad=0.3", facecolor="wheat", alpha=0.7),
|
|
609
|
+
)
|
|
610
|
+
ax.set_xlabel("Times a chunk was hit (capped at 100)")
|
|
611
|
+
ax.set_ylabel("Number of unique chunks")
|
|
612
|
+
ax.set_title("3. Chunk reuse count")
|
|
613
|
+
|
|
614
|
+
# ------------------------------------------------------------------
|
|
615
|
+
# Plot 4 — Rolling token hit rate over time
|
|
616
|
+
# ------------------------------------------------------------------
|
|
617
|
+
ax = axes[1, 0]
|
|
618
|
+
ax.plot(range(1, len(rolling) + 1), rolling, linewidth=1.5)
|
|
619
|
+
ax.set_xlabel("Request index")
|
|
620
|
+
ax.set_ylabel("Cumulative token hit rate (%)")
|
|
621
|
+
ax.set_title("4. Rolling token hit rate over time")
|
|
622
|
+
ax.set_ylim(0, 100)
|
|
623
|
+
ax.grid(True, linestyle="--", alpha=0.5)
|
|
624
|
+
|
|
625
|
+
# ------------------------------------------------------------------
|
|
626
|
+
# Plot 5 — Input length distribution
|
|
627
|
+
# ------------------------------------------------------------------
|
|
628
|
+
ax = axes[1, 1]
|
|
629
|
+
ax.hist(input_lengths, bins=50, edgecolor="black", linewidth=0.4)
|
|
630
|
+
ax.set_xlabel("Input length (tokens / seq_len)")
|
|
631
|
+
ax.set_ylabel("Number of requests")
|
|
632
|
+
ax.set_title("5. Input length per request")
|
|
633
|
+
|
|
634
|
+
# ------------------------------------------------------------------
|
|
635
|
+
# Plot 6 — Global span distribution
|
|
636
|
+
# ------------------------------------------------------------------
|
|
637
|
+
ax = axes[1, 2]
|
|
638
|
+
if global_spans:
|
|
639
|
+
ax.hist(global_spans, bins=50, edgecolor="black", linewidth=0.4)
|
|
640
|
+
ax.set_xlabel("Global span (chunks between last store and hit)")
|
|
641
|
+
ax.set_ylabel("Number of hit chunks")
|
|
642
|
+
ax.set_title("6. Global span distribution")
|
|
643
|
+
|
|
644
|
+
# ------------------------------------------------------------------
|
|
645
|
+
# Plot 7 — Cache position at hit time
|
|
646
|
+
# ------------------------------------------------------------------
|
|
647
|
+
ax = axes[1, 3]
|
|
648
|
+
if cache_positions:
|
|
649
|
+
ax.hist(cache_positions, bins=50, edgecolor="black", linewidth=0.4)
|
|
650
|
+
ax.set_xlabel("Cache position (0 = MRU, max = LRU)")
|
|
651
|
+
ax.set_ylabel("Number of hit chunks")
|
|
652
|
+
ax.set_title("7. Cache position at hit")
|
|
653
|
+
|
|
654
|
+
fig.tight_layout()
|
|
655
|
+
fig.savefig(output, dpi=150)
|
|
656
|
+
print(f"\nStats plot saved to '{output}'")
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
# ---------------------------------------------------------------------------
|
|
660
|
+
# CLI helpers — shared between the module entry point and lmcache tool
|
|
661
|
+
# ---------------------------------------------------------------------------
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def add_simulate_arguments(parser: argparse.ArgumentParser) -> None:
|
|
665
|
+
"""Register all ``simulate`` CLI flags onto *parser*.
|
|
666
|
+
|
|
667
|
+
Called by both the module ``main()`` and by
|
|
668
|
+
:class:`~lmcache.cli.commands.tool.ToolCommand` so that flag definitions
|
|
669
|
+
live in exactly one place.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
parser: The ``ArgumentParser`` (or sub-parser) to add flags to.
|
|
673
|
+
"""
|
|
674
|
+
parser.add_argument(
|
|
675
|
+
"-i",
|
|
676
|
+
"--input",
|
|
677
|
+
nargs="+",
|
|
678
|
+
required=True,
|
|
679
|
+
metavar="PATH",
|
|
680
|
+
help="One or more lookup-hash JSONL files or directories",
|
|
681
|
+
)
|
|
682
|
+
parser.add_argument(
|
|
683
|
+
"-n",
|
|
684
|
+
"--max-samples",
|
|
685
|
+
type=int,
|
|
686
|
+
default=None,
|
|
687
|
+
metavar="N",
|
|
688
|
+
help="Maximum number of events to process (default: all)",
|
|
689
|
+
)
|
|
690
|
+
parser.add_argument(
|
|
691
|
+
"--cache-capacity-gib",
|
|
692
|
+
type=float,
|
|
693
|
+
required=True,
|
|
694
|
+
metavar="GiB",
|
|
695
|
+
help="Cache capacity in gibibytes",
|
|
696
|
+
)
|
|
697
|
+
parser.add_argument(
|
|
698
|
+
"--kv-bytes-per-chunk",
|
|
699
|
+
type=int,
|
|
700
|
+
default=None,
|
|
701
|
+
metavar="BYTES",
|
|
702
|
+
help=(
|
|
703
|
+
"Bytes consumed by one cached chunk. "
|
|
704
|
+
"Auto-computed from the first event's shapes/dtypes if omitted."
|
|
705
|
+
),
|
|
706
|
+
)
|
|
707
|
+
parser.add_argument(
|
|
708
|
+
"--model",
|
|
709
|
+
default=None,
|
|
710
|
+
metavar="NAME",
|
|
711
|
+
help="Filter events by model_name (exact match)",
|
|
712
|
+
)
|
|
713
|
+
parser.add_argument(
|
|
714
|
+
"-o",
|
|
715
|
+
"--output",
|
|
716
|
+
default="cache_stats.png",
|
|
717
|
+
metavar="FILE",
|
|
718
|
+
help="Output image path (default: cache_stats.png)",
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def run_simulate(args: argparse.Namespace) -> None:
|
|
723
|
+
"""Execute the simulate workflow from a parsed argument namespace.
|
|
724
|
+
|
|
725
|
+
Loads events, resolves ``kv_bytes_per_chunk``, runs the simulator, prints
|
|
726
|
+
a text report, and saves a statistics PNG. Called by both the module
|
|
727
|
+
``main()`` and by :class:`~lmcache.cli.commands.tool.ToolCommand`.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
args: Parsed CLI arguments. Must have the attributes registered by
|
|
731
|
+
:func:`add_simulate_arguments`.
|
|
732
|
+
"""
|
|
733
|
+
paths = [Path(p) for p in args.input]
|
|
734
|
+
print(f"Loading lookup events from {[str(p) for p in paths]} …")
|
|
735
|
+
events = load_lookup_events(paths, model=args.model, max_samples=args.max_samples)
|
|
736
|
+
print(f"Loaded {len(events):,} event(s)")
|
|
737
|
+
|
|
738
|
+
if not events:
|
|
739
|
+
print("No events to process.")
|
|
740
|
+
sys.exit(0)
|
|
741
|
+
|
|
742
|
+
kv_bpc = args.kv_bytes_per_chunk
|
|
743
|
+
if kv_bpc is None:
|
|
744
|
+
kv_bpc = compute_kv_bytes_per_chunk(events[0])
|
|
745
|
+
if kv_bpc == 0:
|
|
746
|
+
print(
|
|
747
|
+
"Error: could not determine kv_bytes_per_chunk from the first event "
|
|
748
|
+
"(shapes/dtypes are empty). Pass --kv-bytes-per-chunk explicitly.",
|
|
749
|
+
file=sys.stderr,
|
|
750
|
+
)
|
|
751
|
+
sys.exit(1)
|
|
752
|
+
print(f"Auto-detected kv_bytes_per_chunk = {kv_bpc:,} bytes")
|
|
753
|
+
|
|
754
|
+
capacity_bytes = int(args.cache_capacity_gib * _GIB)
|
|
755
|
+
|
|
756
|
+
print("\nSimulation parameters:")
|
|
757
|
+
print(
|
|
758
|
+
f" Cache capacity : {args.cache_capacity_gib:.2f} GiB "
|
|
759
|
+
f"({capacity_bytes:,} bytes)"
|
|
760
|
+
)
|
|
761
|
+
print(f" KV bytes/chunk : {kv_bpc:,}")
|
|
762
|
+
chunk_sz = events[0].get("chunk_size", "?")
|
|
763
|
+
print(f" Chunk size : {chunk_sz} tokens")
|
|
764
|
+
if args.model:
|
|
765
|
+
print(f" Model filter : {args.model}")
|
|
766
|
+
print()
|
|
767
|
+
|
|
768
|
+
results = simulate(events, capacity_bytes, kv_bpc)
|
|
769
|
+
print_statistics(results)
|
|
770
|
+
plot_statistics(results, events, args.output)
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
# ---------------------------------------------------------------------------
|
|
774
|
+
# CLI entry point
|
|
775
|
+
# ---------------------------------------------------------------------------
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def main() -> None:
|
|
779
|
+
"""CLI entry point for ``python -m lmcache.tools.cache_simulator.simulator``.
|
|
780
|
+
|
|
781
|
+
Parses command-line arguments and delegates to :func:`run_simulate`.
|
|
782
|
+
"""
|
|
783
|
+
parser = argparse.ArgumentParser(
|
|
784
|
+
description=(
|
|
785
|
+
"Simulate LRU token cache hit rate from lookup-hash JSONL logs. "
|
|
786
|
+
"Prints a text report and saves a multi-panel statistics chart."
|
|
787
|
+
)
|
|
788
|
+
)
|
|
789
|
+
add_simulate_arguments(parser)
|
|
790
|
+
args = parser.parse_args()
|
|
791
|
+
run_simulate(args)
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
if __name__ == "__main__":
|
|
795
|
+
main()
|