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,1090 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
3
|
+
# Standard
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
# Third Party
|
|
10
|
+
import torch
|
|
11
|
+
import zmq
|
|
12
|
+
|
|
13
|
+
# First Party
|
|
14
|
+
from lmcache.integration.request_telemetry.factory import RequestTelemetryFactory
|
|
15
|
+
from lmcache.utils import _lmcache_nvtx_annotate, init_logger
|
|
16
|
+
from lmcache.v1.multiprocess.custom_types import (
|
|
17
|
+
BlockAllocationRecord,
|
|
18
|
+
CudaIPCWrapper,
|
|
19
|
+
IPCCacheEngineKey,
|
|
20
|
+
KVCache,
|
|
21
|
+
)
|
|
22
|
+
from lmcache.v1.multiprocess.mq import MessageQueueClient, MessagingFuture
|
|
23
|
+
from lmcache.v1.multiprocess.protocol import RequestType, get_response_class
|
|
24
|
+
from lmcache.v1.periodic_thread import PeriodicThread, ThreadLevel, ThreadRunSummary
|
|
25
|
+
|
|
26
|
+
logger = init_logger(__name__)
|
|
27
|
+
|
|
28
|
+
# Timeout (seconds) for blocking MQ requests: initial chunk-size query,
|
|
29
|
+
# KV cache registration/unregistration, and other synchronous operations.
|
|
30
|
+
DEFAULT_MQ_TIMEOUT: float = 300.0
|
|
31
|
+
# Interval (seconds) between periodic heartbeat pings to the server.
|
|
32
|
+
DEFAULT_HEARTBEAT_INTERVAL: float = 10.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def wrap_kv_caches(kv_caches: dict[str, torch.Tensor]) -> KVCache:
|
|
36
|
+
logger.info("KV caches keys are %s", list(kv_caches.keys()))
|
|
37
|
+
return [CudaIPCWrapper(tensor) for tensor in kv_caches.values()]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def send_lmcache_request(
|
|
41
|
+
mq_client: MessageQueueClient,
|
|
42
|
+
request_type: RequestType,
|
|
43
|
+
payloads: list[Any],
|
|
44
|
+
) -> MessagingFuture[Any]:
|
|
45
|
+
"""
|
|
46
|
+
Helper function to send the request to the LMCache multiprocess server
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
mq_client: The LMCache multiprocess mode message queue client
|
|
50
|
+
request_type: The request type
|
|
51
|
+
payloads: The request payloads
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A messaging future for the request
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
future = mq_client.submit_request(
|
|
58
|
+
request_type, payloads, get_response_class(request_type)
|
|
59
|
+
)
|
|
60
|
+
return future
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_lmcache_chunk_size(
|
|
64
|
+
mq_client: MessageQueueClient,
|
|
65
|
+
) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Helper function to get the LMCache chunk size from the server
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
mq_client: The LMCache multiprocess mode message queue client
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
An integer representing the LMCache chunk size
|
|
74
|
+
"""
|
|
75
|
+
future = send_lmcache_request(mq_client, RequestType.GET_CHUNK_SIZE, [])
|
|
76
|
+
chunk_size = future.result(timeout=DEFAULT_MQ_TIMEOUT)
|
|
77
|
+
return chunk_size
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def send_ping(
|
|
81
|
+
mq_client: MessageQueueClient,
|
|
82
|
+
timeout: float,
|
|
83
|
+
) -> bool:
|
|
84
|
+
"""Send a PING request and return the result.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if server is healthy, False on timeout or error.
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
future = send_lmcache_request(mq_client, RequestType.PING, [])
|
|
91
|
+
return future.result(timeout=timeout)
|
|
92
|
+
except TimeoutError:
|
|
93
|
+
return False
|
|
94
|
+
except Exception:
|
|
95
|
+
logger.debug("Ping failed with exception", exc_info=True)
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class ParallelStrategy:
|
|
101
|
+
use_mla: bool
|
|
102
|
+
"""Whether to use the MLA."""
|
|
103
|
+
|
|
104
|
+
kv_world_size: int
|
|
105
|
+
"""
|
|
106
|
+
The kv world size, kv_world_size may not be equal to the actual_world_size,
|
|
107
|
+
in the case of mla, it will 'exclude' the effect of TP, the value is
|
|
108
|
+
calculated by `extract_world_size_and_kv_rank` in `lmcache_mp_connector.py`.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
kv_worker_id: int
|
|
112
|
+
"""
|
|
113
|
+
The kv worker id of the sub-process, kv_worker_id may not be equal to the
|
|
114
|
+
actual_worker_id, in the case of mla, it will 'exclude' the effect of TP,
|
|
115
|
+
the value is calculated by `extract_world_size_and_kv_rank` in
|
|
116
|
+
`lmcache_mp_connector.py`.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
actual_world_size: int
|
|
120
|
+
"""The actual world size."""
|
|
121
|
+
|
|
122
|
+
actual_worker_id: int
|
|
123
|
+
"""The actual worker id of the sub-process."""
|
|
124
|
+
|
|
125
|
+
tp_size: int
|
|
126
|
+
"""The tensor parallel size."""
|
|
127
|
+
|
|
128
|
+
pp_size: int
|
|
129
|
+
"""The pipeline parallel size."""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class HeartbeatThread(PeriodicThread):
|
|
133
|
+
"""Periodically checks server health via PING.
|
|
134
|
+
|
|
135
|
+
Manages a threading.Event that adapters use to gate operations.
|
|
136
|
+
When unhealthy, the adapter enters degraded mode; if the server
|
|
137
|
+
recovers, the adapter automatically resumes normal operation.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
mq_client: MessageQueueClient,
|
|
143
|
+
health_event: threading.Event,
|
|
144
|
+
interval: float = DEFAULT_HEARTBEAT_INTERVAL,
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
Args:
|
|
148
|
+
mq_client: The message queue client used to send PING requests.
|
|
149
|
+
health_event: A threading.Event shared with the adapter.
|
|
150
|
+
Set when the server is healthy, cleared when unhealthy.
|
|
151
|
+
Adapters check this event to decide whether to proceed
|
|
152
|
+
with operations or enter degraded mode.
|
|
153
|
+
interval: Seconds between heartbeat pings and ping timeout.
|
|
154
|
+
"""
|
|
155
|
+
super().__init__(
|
|
156
|
+
name="lmcache-heartbeat",
|
|
157
|
+
interval=interval,
|
|
158
|
+
level=ThreadLevel.CRITICAL,
|
|
159
|
+
)
|
|
160
|
+
self._mq_client = mq_client
|
|
161
|
+
self._health_event = health_event
|
|
162
|
+
self._interval = interval
|
|
163
|
+
|
|
164
|
+
def _execute(self) -> ThreadRunSummary:
|
|
165
|
+
was_healthy = self._health_event.is_set()
|
|
166
|
+
healthy = send_ping(self._mq_client, timeout=self._interval)
|
|
167
|
+
|
|
168
|
+
if healthy:
|
|
169
|
+
self._health_event.set()
|
|
170
|
+
if not was_healthy:
|
|
171
|
+
logger.warning(
|
|
172
|
+
"LMCache server is healthy again — resuming normal operation"
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
self._health_event.clear()
|
|
176
|
+
if was_healthy:
|
|
177
|
+
logger.warning("LMCache server is unhealthy — entering degraded mode")
|
|
178
|
+
|
|
179
|
+
return ThreadRunSummary(
|
|
180
|
+
success=True,
|
|
181
|
+
message="healthy" if healthy else "unhealthy",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class LoadStoreOp:
|
|
187
|
+
token_ids: list[int]
|
|
188
|
+
"""Token IDs for the load/store operation"""
|
|
189
|
+
|
|
190
|
+
block_ids: list[int]
|
|
191
|
+
"""Block ids for the load/store operation"""
|
|
192
|
+
|
|
193
|
+
start: int = 0
|
|
194
|
+
"""Start token index"""
|
|
195
|
+
|
|
196
|
+
end: int = 0
|
|
197
|
+
"""End token index"""
|
|
198
|
+
|
|
199
|
+
skip_first_n_tokens: int = 0
|
|
200
|
+
"""Number of tokens to skip writing at the beginning of the retrieve
|
|
201
|
+
range. Used to avoid overwriting APC-shared GPU blocks during retrieve."""
|
|
202
|
+
|
|
203
|
+
def __len__(self) -> int:
|
|
204
|
+
return len(self.block_ids)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
StoreResult = bool
|
|
208
|
+
RetrieveResult = bool
|
|
209
|
+
LookupResult = int
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class LMCacheMPSchedulerAdapter:
|
|
213
|
+
# Signature kept backward-compatible with the vllm-bundled caller
|
|
214
|
+
# at vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py,
|
|
215
|
+
# which still passes (world_size, kv_rank, vllm_block_size) positionally
|
|
216
|
+
# and `mq_timeout` as a kwarg. Pass `parallel_strategy` for the new form.
|
|
217
|
+
def __init__(
|
|
218
|
+
self,
|
|
219
|
+
server_url: str,
|
|
220
|
+
context: zmq.Context,
|
|
221
|
+
model_name: str,
|
|
222
|
+
world_size: int = 1,
|
|
223
|
+
kv_rank: int = 0,
|
|
224
|
+
vllm_block_size: int = 16,
|
|
225
|
+
tp_size: int = 1,
|
|
226
|
+
parallel_strategy: Optional[ParallelStrategy] = None,
|
|
227
|
+
mq_timeout: float = DEFAULT_MQ_TIMEOUT,
|
|
228
|
+
heartbeat_interval: float = DEFAULT_HEARTBEAT_INTERVAL,
|
|
229
|
+
):
|
|
230
|
+
"""
|
|
231
|
+
Args:
|
|
232
|
+
server_url: The server URL for the LMCache message queue
|
|
233
|
+
context: The ZMQ context
|
|
234
|
+
model_name: The model name used for LMCache keys
|
|
235
|
+
vllm_block_size: The block size used in vLLM
|
|
236
|
+
parallel_strategy:
|
|
237
|
+
The parallel strategy, which includes `use_mla`,
|
|
238
|
+
`kv_world_size`, `kv_worker_id` and so on. If None,
|
|
239
|
+
synthesised from world_size/kv_rank/tp_size.
|
|
240
|
+
mq_timeout: Timeout in seconds for message queue requests.
|
|
241
|
+
heartbeat_interval: Interval in seconds between heartbeat pings.
|
|
242
|
+
"""
|
|
243
|
+
if parallel_strategy is None:
|
|
244
|
+
parallel_strategy = ParallelStrategy(
|
|
245
|
+
use_mla=False,
|
|
246
|
+
kv_world_size=world_size,
|
|
247
|
+
kv_worker_id=kv_rank,
|
|
248
|
+
actual_world_size=world_size,
|
|
249
|
+
actual_worker_id=kv_rank,
|
|
250
|
+
tp_size=tp_size,
|
|
251
|
+
pp_size=1,
|
|
252
|
+
)
|
|
253
|
+
self.mq_client = MessageQueueClient(server_url, context)
|
|
254
|
+
self._mq_timeout = mq_timeout
|
|
255
|
+
|
|
256
|
+
# Lookup state tracking:
|
|
257
|
+
# - _pending_lookups: request_ids submitted but not yet resolved
|
|
258
|
+
# - _finished_lookup_results: cached chunk count keyed by request_id,
|
|
259
|
+
# so that repeated calls to check_lookup_result return the same value
|
|
260
|
+
# even after the server has already popped the job (exactly-once).
|
|
261
|
+
self._pending_lookups: set[str] = set()
|
|
262
|
+
self._finished_lookup_results: dict[str, int] = {}
|
|
263
|
+
|
|
264
|
+
self.model_name = model_name
|
|
265
|
+
self.parallel_strategy = parallel_strategy
|
|
266
|
+
|
|
267
|
+
# Read chunk size from lmcache
|
|
268
|
+
try:
|
|
269
|
+
self.chunk_size = get_lmcache_chunk_size(self.mq_client)
|
|
270
|
+
except TimeoutError:
|
|
271
|
+
self.mq_client.close()
|
|
272
|
+
raise ConnectionError(
|
|
273
|
+
f"LMCache server did not respond within {mq_timeout}s. "
|
|
274
|
+
"Is the server running?"
|
|
275
|
+
) from None
|
|
276
|
+
assert self.chunk_size % vllm_block_size == 0, (
|
|
277
|
+
"LMCache chunk size should be a multiple of vLLM block size"
|
|
278
|
+
)
|
|
279
|
+
self.blocks_in_chunk = self.chunk_size // vllm_block_size
|
|
280
|
+
|
|
281
|
+
# Health state (shared with heartbeat thread)
|
|
282
|
+
self._health_event = threading.Event()
|
|
283
|
+
self._health_event.set()
|
|
284
|
+
|
|
285
|
+
# Heartbeat thread is created but NOT started yet.
|
|
286
|
+
# It will be lazily started on the first lookup
|
|
287
|
+
# request, by which time vLLM is fully ready.
|
|
288
|
+
self._heartbeat_interval = heartbeat_interval
|
|
289
|
+
self._heartbeat: HeartbeatThread | None = None
|
|
290
|
+
self._heartbeat_lock = threading.Lock()
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def world_size(self) -> int:
|
|
294
|
+
"""Get the kv world size."""
|
|
295
|
+
return self.parallel_strategy.kv_world_size
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def tp_size(self) -> int:
|
|
299
|
+
"""The tensor parallel size."""
|
|
300
|
+
return self.parallel_strategy.tp_size
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def is_healthy(self) -> bool:
|
|
304
|
+
"""Whether the LMCache server is healthy."""
|
|
305
|
+
return self._health_event.is_set()
|
|
306
|
+
|
|
307
|
+
def _ensure_heartbeat_started(self) -> None:
|
|
308
|
+
"""Lazily start the heartbeat thread on first use."""
|
|
309
|
+
if self._heartbeat is not None:
|
|
310
|
+
return
|
|
311
|
+
with self._heartbeat_lock:
|
|
312
|
+
if self._heartbeat is not None:
|
|
313
|
+
return
|
|
314
|
+
self._heartbeat = HeartbeatThread(
|
|
315
|
+
mq_client=self.mq_client,
|
|
316
|
+
health_event=self._health_event,
|
|
317
|
+
interval=self._heartbeat_interval,
|
|
318
|
+
)
|
|
319
|
+
self._heartbeat.start()
|
|
320
|
+
|
|
321
|
+
@_lmcache_nvtx_annotate
|
|
322
|
+
def maybe_submit_lookup_request(
|
|
323
|
+
self,
|
|
324
|
+
request_id: str,
|
|
325
|
+
token_ids: list[int],
|
|
326
|
+
cache_salt: str = "",
|
|
327
|
+
):
|
|
328
|
+
"""
|
|
329
|
+
Submit a new lookup request to LMCache if there is no ongoing request.
|
|
330
|
+
|
|
331
|
+
Sends a LOOKUP request to the server and blocks until a prefetch
|
|
332
|
+
job ID is returned. The actual prefetch result can then be polled
|
|
333
|
+
via ``check_lookup_result``.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
request_id: The ID of the lookup request. The same ID indicates it's
|
|
337
|
+
from the same request
|
|
338
|
+
token_ids: Token IDs to lookup from LMCache
|
|
339
|
+
cache_salt: Per-user isolation salt. Requests with different
|
|
340
|
+
cache_salt values produce separate cache entries.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
None
|
|
344
|
+
|
|
345
|
+
Notes:
|
|
346
|
+
This function will have a side-effect: submitting a look up request to
|
|
347
|
+
LMCache, which will essentially 'lock' the KV cache chunks in the LMCache
|
|
348
|
+
for later retrieve operations.
|
|
349
|
+
In the meantime, this function will record the lookup request, and the
|
|
350
|
+
status of the look up request can be checked by `check_lookup_result`.
|
|
351
|
+
"""
|
|
352
|
+
self._ensure_heartbeat_started()
|
|
353
|
+
|
|
354
|
+
if not self.is_healthy:
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
if request_id in self._pending_lookups:
|
|
358
|
+
# Skip if there is already a lookup request
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
aligned_end = (len(token_ids) // self.chunk_size) * self.chunk_size
|
|
362
|
+
|
|
363
|
+
key = self._create_key(
|
|
364
|
+
token_ids,
|
|
365
|
+
start=0,
|
|
366
|
+
end=aligned_end,
|
|
367
|
+
request_id=request_id,
|
|
368
|
+
cache_salt=cache_salt,
|
|
369
|
+
).no_worker_id_version()
|
|
370
|
+
|
|
371
|
+
future = send_lmcache_request(
|
|
372
|
+
self.mq_client,
|
|
373
|
+
RequestType.LOOKUP,
|
|
374
|
+
[key, self.tp_size],
|
|
375
|
+
)
|
|
376
|
+
try:
|
|
377
|
+
future.result(timeout=self._mq_timeout)
|
|
378
|
+
except TimeoutError:
|
|
379
|
+
logger.warning(
|
|
380
|
+
"LOOKUP request timed out after %ss. Marking server as unhealthy.",
|
|
381
|
+
self._mq_timeout,
|
|
382
|
+
)
|
|
383
|
+
self._health_event.clear()
|
|
384
|
+
return
|
|
385
|
+
self._pending_lookups.add(request_id)
|
|
386
|
+
|
|
387
|
+
@_lmcache_nvtx_annotate
|
|
388
|
+
def check_lookup_result(self, request_id: str) -> int | None:
|
|
389
|
+
"""
|
|
390
|
+
Check the result of a previously submitted lookup request.
|
|
391
|
+
|
|
392
|
+
Sends a QUERY_PREFETCH_STATUS request to the server and blocks
|
|
393
|
+
until the server responds. Returns the matched token count
|
|
394
|
+
when the prefetch is complete, or None if still in progress.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
request_id: The ID of the lookup request submitted in
|
|
398
|
+
`maybe_submit_lookup_request`
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
An integer representing the total number of tokens matched
|
|
402
|
+
in LMCache (prefix matching), or
|
|
403
|
+
None if the lookup request is not finished yet.
|
|
404
|
+
"""
|
|
405
|
+
if request_id not in self._pending_lookups:
|
|
406
|
+
# No job — either unhealthy at submit time or already cleaned up.
|
|
407
|
+
# If we have a cached result, return it to handle repeated calls.
|
|
408
|
+
return self._finished_lookup_results.get(request_id, 0)
|
|
409
|
+
|
|
410
|
+
if not self.is_healthy:
|
|
411
|
+
# Server went down — give up on this lookup
|
|
412
|
+
return 0
|
|
413
|
+
|
|
414
|
+
if request_id in self._finished_lookup_results:
|
|
415
|
+
# Return cached result if the job is already finished
|
|
416
|
+
return self._finished_lookup_results[request_id]
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
result = send_lmcache_request(
|
|
420
|
+
self.mq_client,
|
|
421
|
+
RequestType.QUERY_PREFETCH_STATUS,
|
|
422
|
+
[request_id],
|
|
423
|
+
).result(timeout=self._mq_timeout)
|
|
424
|
+
except TimeoutError:
|
|
425
|
+
logger.warning(
|
|
426
|
+
"QUERY_PREFETCH_STATUS timed out after %ss. "
|
|
427
|
+
"Marking server as unhealthy.",
|
|
428
|
+
self._mq_timeout,
|
|
429
|
+
)
|
|
430
|
+
self._health_event.clear()
|
|
431
|
+
return 0
|
|
432
|
+
|
|
433
|
+
if result is None:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
token_count = result * self.chunk_size
|
|
437
|
+
self._finished_lookup_results[request_id] = token_count
|
|
438
|
+
return token_count
|
|
439
|
+
|
|
440
|
+
def num_blocks_per_chunk(self) -> int:
|
|
441
|
+
"""
|
|
442
|
+
Returns:
|
|
443
|
+
The number of vllm blocks in a LMCache data chunk
|
|
444
|
+
"""
|
|
445
|
+
return self.blocks_in_chunk
|
|
446
|
+
|
|
447
|
+
def cleanup_lookup_result(self, request_id: str) -> None:
|
|
448
|
+
"""
|
|
449
|
+
Clean up lookup state for a finished request to prevent memory leak.
|
|
450
|
+
Args:
|
|
451
|
+
request_id: The ID of the finished request.
|
|
452
|
+
"""
|
|
453
|
+
self._pending_lookups.discard(request_id)
|
|
454
|
+
self._finished_lookup_results.pop(request_id, None)
|
|
455
|
+
|
|
456
|
+
def free_lookup_locks(
|
|
457
|
+
self,
|
|
458
|
+
token_ids: list[int],
|
|
459
|
+
start: int,
|
|
460
|
+
end: int,
|
|
461
|
+
request_id: str,
|
|
462
|
+
cache_salt: str = "",
|
|
463
|
+
) -> None:
|
|
464
|
+
"""Release read locks acquired during lookup without a full retrieve.
|
|
465
|
+
|
|
466
|
+
Use this when some chunks matched by lookup overlap with blocks that
|
|
467
|
+
vLLM has already computed, so they will never be retrieved. Calling
|
|
468
|
+
this prevents those chunks from holding read locks until TTL expiry.
|
|
469
|
+
|
|
470
|
+
Or use this when a request is cancelled or aborted after lookup but
|
|
471
|
+
before retrieve to avoid holding read locks until TTL expiry.
|
|
472
|
+
|
|
473
|
+
When ``start`` or ``end`` is not aligned to the chunk size, the
|
|
474
|
+
entire chunk containing start boundary is freed but not end boundary.
|
|
475
|
+
It is caller's responsibility to properly align the boundaries.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
token_ids: Token IDs for the key (same as used in lookup).
|
|
479
|
+
start: Start token index.
|
|
480
|
+
end: End token index.
|
|
481
|
+
request_id: The request ID.
|
|
482
|
+
cache_salt: Per-user isolation salt.
|
|
483
|
+
"""
|
|
484
|
+
if not self.is_healthy:
|
|
485
|
+
return
|
|
486
|
+
|
|
487
|
+
key = self._create_key(
|
|
488
|
+
token_ids,
|
|
489
|
+
start=start,
|
|
490
|
+
end=end,
|
|
491
|
+
request_id=request_id,
|
|
492
|
+
cache_salt=cache_salt,
|
|
493
|
+
).no_worker_id_version()
|
|
494
|
+
send_lmcache_request(
|
|
495
|
+
self.mq_client,
|
|
496
|
+
RequestType.FREE_LOOKUP_LOCKS,
|
|
497
|
+
[key, self.tp_size],
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
def end_session(self, request_id: str) -> None:
|
|
501
|
+
"""
|
|
502
|
+
Notify LMCache server to remove the session for a finished request.
|
|
503
|
+
Args:
|
|
504
|
+
request_id: The ID of the finished request.
|
|
505
|
+
"""
|
|
506
|
+
if not self.is_healthy:
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
send_lmcache_request(
|
|
510
|
+
self.mq_client,
|
|
511
|
+
RequestType.END_SESSION,
|
|
512
|
+
[request_id],
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
def report_block_allocations(
|
|
516
|
+
self,
|
|
517
|
+
records: list[BlockAllocationRecord],
|
|
518
|
+
) -> None:
|
|
519
|
+
"""Report vLLM GPU block allocation deltas to LMCache server.
|
|
520
|
+
|
|
521
|
+
Fire-and-forget: does not wait for a response. If the server
|
|
522
|
+
is unhealthy the report is silently dropped.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
records: List of BlockAllocationRecord with per-request
|
|
526
|
+
block and token allocation deltas.
|
|
527
|
+
"""
|
|
528
|
+
if not self.is_healthy or not records:
|
|
529
|
+
return
|
|
530
|
+
|
|
531
|
+
send_lmcache_request(
|
|
532
|
+
self.mq_client,
|
|
533
|
+
RequestType.REPORT_BLOCK_ALLOCATION,
|
|
534
|
+
[os.getpid(), self.model_name, records],
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# Helper functions
|
|
538
|
+
def _create_key(
|
|
539
|
+
self,
|
|
540
|
+
token_ids: list[int],
|
|
541
|
+
start: int,
|
|
542
|
+
end: int,
|
|
543
|
+
request_id: str,
|
|
544
|
+
cache_salt: str = "",
|
|
545
|
+
) -> IPCCacheEngineKey:
|
|
546
|
+
"""Convert token IDs to an IPC cache engine key.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
token_ids: The token IDs.
|
|
550
|
+
start: Start token index.
|
|
551
|
+
end: End token index.
|
|
552
|
+
request_id: The request ID.
|
|
553
|
+
cache_salt: Per-user isolation salt.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
IPCCacheEngineKey: The constructed key.
|
|
557
|
+
"""
|
|
558
|
+
# NOTE: for the scheduler adapter, we don't have a worker id,
|
|
559
|
+
# so we set it to None in the key.
|
|
560
|
+
return IPCCacheEngineKey(
|
|
561
|
+
model_name=self.model_name,
|
|
562
|
+
world_size=self.world_size,
|
|
563
|
+
worker_id=None,
|
|
564
|
+
token_ids=tuple(token_ids),
|
|
565
|
+
start=start,
|
|
566
|
+
end=end,
|
|
567
|
+
request_id=request_id,
|
|
568
|
+
cache_salt=cache_salt,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class LMCacheMPWorkerAdapter:
|
|
573
|
+
# Signature kept backward-compatible with the vllm-bundled caller; see
|
|
574
|
+
# LMCacheMPSchedulerAdapter above for details.
|
|
575
|
+
def __init__(
|
|
576
|
+
self,
|
|
577
|
+
server_url: str,
|
|
578
|
+
context: zmq.Context,
|
|
579
|
+
model_name: str,
|
|
580
|
+
world_size: int = 1,
|
|
581
|
+
kv_rank: int = 0,
|
|
582
|
+
vllm_block_size: int = 16,
|
|
583
|
+
tp_size: int = 1,
|
|
584
|
+
parallel_strategy: Optional[ParallelStrategy] = None,
|
|
585
|
+
mq_timeout: float = DEFAULT_MQ_TIMEOUT,
|
|
586
|
+
heartbeat_interval: float = DEFAULT_HEARTBEAT_INTERVAL,
|
|
587
|
+
):
|
|
588
|
+
if parallel_strategy is None:
|
|
589
|
+
parallel_strategy = ParallelStrategy(
|
|
590
|
+
use_mla=False,
|
|
591
|
+
kv_world_size=world_size,
|
|
592
|
+
kv_worker_id=kv_rank,
|
|
593
|
+
actual_world_size=world_size,
|
|
594
|
+
actual_worker_id=kv_rank,
|
|
595
|
+
tp_size=tp_size,
|
|
596
|
+
pp_size=1,
|
|
597
|
+
)
|
|
598
|
+
self.mq_client = MessageQueueClient(server_url, context)
|
|
599
|
+
self._mq_timeout = mq_timeout
|
|
600
|
+
|
|
601
|
+
# Instance id for GPU worker
|
|
602
|
+
self.instance_id = os.getpid()
|
|
603
|
+
|
|
604
|
+
# Registered kv caches from vLLM
|
|
605
|
+
self.kv_caches: dict[str, torch.Tensor] = {}
|
|
606
|
+
|
|
607
|
+
# Request futures
|
|
608
|
+
self.store_futures: dict[str, MessagingFuture[StoreResult]] = {}
|
|
609
|
+
# request_id -> (future, block_ids)
|
|
610
|
+
self.retrieve_futures: dict[
|
|
611
|
+
str, tuple[MessagingFuture[RetrieveResult], list[int]]
|
|
612
|
+
] = {}
|
|
613
|
+
|
|
614
|
+
# Block IDs that failed due to retrieve timeout
|
|
615
|
+
self.error_block_ids: set[int] = set()
|
|
616
|
+
|
|
617
|
+
# The store requests that have finished execution in LMCache
|
|
618
|
+
self.finished_stores: set[str] = set()
|
|
619
|
+
# The finished request ids that are passed via vLLM and also
|
|
620
|
+
# have corresponding store requests submitted to LMCache before
|
|
621
|
+
self.previously_finished: set[str] = set()
|
|
622
|
+
# Request IDs already returned as finished_sending to the scheduler.
|
|
623
|
+
# Prevents re-reporting the same ID after drain clears tracking sets.
|
|
624
|
+
self._returned_finished: set[str] = set()
|
|
625
|
+
|
|
626
|
+
self.model_name = model_name
|
|
627
|
+
self.parallel_strategy = parallel_strategy
|
|
628
|
+
|
|
629
|
+
# Read chunk size from lmcache
|
|
630
|
+
try:
|
|
631
|
+
chunk_size = get_lmcache_chunk_size(self.mq_client)
|
|
632
|
+
except TimeoutError:
|
|
633
|
+
self.mq_client.close()
|
|
634
|
+
raise ConnectionError(
|
|
635
|
+
f"LMCache server did not respond within {mq_timeout}s. "
|
|
636
|
+
"Is the server running?"
|
|
637
|
+
) from None
|
|
638
|
+
assert chunk_size % vllm_block_size == 0, (
|
|
639
|
+
"LMCache chunk size should be a multiple of vLLM block size"
|
|
640
|
+
)
|
|
641
|
+
self.blocks_in_chunk = chunk_size // vllm_block_size
|
|
642
|
+
|
|
643
|
+
# Health state (shared with heartbeat thread)
|
|
644
|
+
self._health_event = threading.Event()
|
|
645
|
+
self._health_event.set()
|
|
646
|
+
|
|
647
|
+
# Heartbeat thread is created but NOT started yet.
|
|
648
|
+
# It will be lazily started on the first store or retrieve
|
|
649
|
+
# request, by which time vLLM is fully ready (model loaded,
|
|
650
|
+
# KV caches allocated, warmup & CUDA graph capture done).
|
|
651
|
+
self._heartbeat_interval = heartbeat_interval
|
|
652
|
+
self._heartbeat: HeartbeatThread | None = None
|
|
653
|
+
self._heartbeat_lock = threading.Lock()
|
|
654
|
+
|
|
655
|
+
# request telemetry, used for prefill-decode disagg
|
|
656
|
+
# TODO: pass down the configuration via vLLM connector config
|
|
657
|
+
# instead of env var
|
|
658
|
+
self.request_telemetry = RequestTelemetryFactory.create(
|
|
659
|
+
telemetry_type=os.getenv("LMCACHE_REQUEST_TELEMETRY_TYPE", "noop"),
|
|
660
|
+
config={
|
|
661
|
+
"endpoint": os.getenv(
|
|
662
|
+
"LMCACHE_REQUEST_TELEMETRY_ENDPOINT",
|
|
663
|
+
"http://localhost:5768/api/v1/telemetry",
|
|
664
|
+
),
|
|
665
|
+
},
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
@property
|
|
669
|
+
def is_healthy(self) -> bool:
|
|
670
|
+
"""Whether the LMCache server is healthy."""
|
|
671
|
+
return self._health_event.is_set()
|
|
672
|
+
|
|
673
|
+
@property
|
|
674
|
+
def world_size(self) -> int:
|
|
675
|
+
"""Get the kv world size."""
|
|
676
|
+
return self.parallel_strategy.kv_world_size
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def worker_id(self) -> int:
|
|
680
|
+
"""Get the kv worker id."""
|
|
681
|
+
return self.parallel_strategy.kv_worker_id
|
|
682
|
+
|
|
683
|
+
@property
|
|
684
|
+
def use_mla(self) -> bool:
|
|
685
|
+
"""Whether to use MLA."""
|
|
686
|
+
return self.parallel_strategy.use_mla
|
|
687
|
+
|
|
688
|
+
@property
|
|
689
|
+
def is_first_rank_of_pp_group(self) -> bool:
|
|
690
|
+
"""Is the first rank of the pipeline parallel group."""
|
|
691
|
+
return (
|
|
692
|
+
self.parallel_strategy.actual_worker_id % self.parallel_strategy.tp_size
|
|
693
|
+
== 0
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
|
|
697
|
+
"""
|
|
698
|
+
Register the kv caches with LMCache server
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
kv_caches: A dict of kv caches to register. The keys are the
|
|
702
|
+
layer names and the values are the corresponding tensors.
|
|
703
|
+
"""
|
|
704
|
+
# First Party
|
|
705
|
+
from lmcache.integration.vllm.utils import vllm_layout_hints
|
|
706
|
+
from lmcache.v1.gpu_connector.utils import (
|
|
707
|
+
ensure_contiguous_kv_caches,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
# Register kv cache and send the request
|
|
711
|
+
logger.info("Registering kv caches")
|
|
712
|
+
|
|
713
|
+
layout_hints = vllm_layout_hints()
|
|
714
|
+
kv_caches = ensure_contiguous_kv_caches(
|
|
715
|
+
kv_caches, kv_layout=layout_hints.get("kv_layout")
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
self.kv_caches = kv_caches
|
|
719
|
+
|
|
720
|
+
future = send_lmcache_request(
|
|
721
|
+
self.mq_client,
|
|
722
|
+
RequestType.REGISTER_KV_CACHE,
|
|
723
|
+
[
|
|
724
|
+
self.instance_id,
|
|
725
|
+
wrap_kv_caches(kv_caches),
|
|
726
|
+
self.model_name,
|
|
727
|
+
self.world_size,
|
|
728
|
+
layout_hints,
|
|
729
|
+
],
|
|
730
|
+
)
|
|
731
|
+
try:
|
|
732
|
+
future.result(timeout=self._mq_timeout)
|
|
733
|
+
except TimeoutError:
|
|
734
|
+
raise ConnectionError(
|
|
735
|
+
"LMCache server did not respond to "
|
|
736
|
+
"register_kv_caches within "
|
|
737
|
+
f"{self._mq_timeout}s. Is the server running?"
|
|
738
|
+
) from None
|
|
739
|
+
|
|
740
|
+
def _ensure_heartbeat_started(self) -> None:
|
|
741
|
+
"""Lazily start the heartbeat thread on first use."""
|
|
742
|
+
if self._heartbeat is not None:
|
|
743
|
+
return
|
|
744
|
+
with self._heartbeat_lock:
|
|
745
|
+
if self._heartbeat is not None:
|
|
746
|
+
return
|
|
747
|
+
self._heartbeat = HeartbeatThread(
|
|
748
|
+
mq_client=self.mq_client,
|
|
749
|
+
health_event=self._health_event,
|
|
750
|
+
interval=self._heartbeat_interval,
|
|
751
|
+
)
|
|
752
|
+
self._heartbeat.start()
|
|
753
|
+
|
|
754
|
+
@_lmcache_nvtx_annotate
|
|
755
|
+
def submit_store_request(
|
|
756
|
+
self,
|
|
757
|
+
request_id: str,
|
|
758
|
+
op: LoadStoreOp,
|
|
759
|
+
event: torch.cuda.Event,
|
|
760
|
+
cache_salt: str = "",
|
|
761
|
+
):
|
|
762
|
+
"""
|
|
763
|
+
Submit a KV cache store request to LMCache
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
request_id: The ID of the request
|
|
767
|
+
op: The LoadStoreOp describing the store operation.
|
|
768
|
+
event: The CUDA event that is recorded after the current
|
|
769
|
+
model inference step
|
|
770
|
+
cache_salt: Per-user isolation salt.
|
|
771
|
+
"""
|
|
772
|
+
self._ensure_heartbeat_started()
|
|
773
|
+
|
|
774
|
+
if not self.is_healthy:
|
|
775
|
+
return
|
|
776
|
+
|
|
777
|
+
assert op.token_ids is not None
|
|
778
|
+
key = self._create_key(
|
|
779
|
+
op.token_ids,
|
|
780
|
+
op.start,
|
|
781
|
+
op.end,
|
|
782
|
+
request_id=request_id,
|
|
783
|
+
cache_salt=cache_salt,
|
|
784
|
+
)
|
|
785
|
+
future = send_lmcache_request(
|
|
786
|
+
self.mq_client,
|
|
787
|
+
RequestType.STORE,
|
|
788
|
+
[key, self.instance_id, op.block_ids, event.ipc_handle()],
|
|
789
|
+
).to_cuda_future()
|
|
790
|
+
self.store_futures[request_id] = future
|
|
791
|
+
|
|
792
|
+
@_lmcache_nvtx_annotate
|
|
793
|
+
def submit_retrieve_request(
|
|
794
|
+
self,
|
|
795
|
+
request_id: str,
|
|
796
|
+
op: LoadStoreOp,
|
|
797
|
+
event: torch.cuda.Event,
|
|
798
|
+
cache_salt: str = "",
|
|
799
|
+
):
|
|
800
|
+
"""
|
|
801
|
+
Submit a KV cache retrieve request to LMCache
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
request_id: The ID of the request
|
|
805
|
+
op: The LoadStoreOp describing the retrieve operation.
|
|
806
|
+
event: The CUDA event that is recorded after the current
|
|
807
|
+
model inference step
|
|
808
|
+
cache_salt: Per-user isolation salt.
|
|
809
|
+
"""
|
|
810
|
+
self._ensure_heartbeat_started()
|
|
811
|
+
|
|
812
|
+
if not self.is_healthy:
|
|
813
|
+
self.error_block_ids.update(op.block_ids)
|
|
814
|
+
return
|
|
815
|
+
|
|
816
|
+
assert op.token_ids is not None
|
|
817
|
+
key = self._create_key(
|
|
818
|
+
op.token_ids,
|
|
819
|
+
op.start,
|
|
820
|
+
op.end,
|
|
821
|
+
request_id=request_id,
|
|
822
|
+
cache_salt=cache_salt,
|
|
823
|
+
)
|
|
824
|
+
future = send_lmcache_request(
|
|
825
|
+
self.mq_client,
|
|
826
|
+
RequestType.RETRIEVE,
|
|
827
|
+
[
|
|
828
|
+
key,
|
|
829
|
+
self.instance_id,
|
|
830
|
+
op.block_ids,
|
|
831
|
+
event.ipc_handle(),
|
|
832
|
+
op.skip_first_n_tokens,
|
|
833
|
+
],
|
|
834
|
+
).to_cuda_future()
|
|
835
|
+
self.retrieve_futures[request_id] = (future, list(op.block_ids))
|
|
836
|
+
|
|
837
|
+
@_lmcache_nvtx_annotate
|
|
838
|
+
def batched_submit_store_requests(
|
|
839
|
+
self,
|
|
840
|
+
request_ids: list[str],
|
|
841
|
+
ops: list[LoadStoreOp],
|
|
842
|
+
event: torch.cuda.Event,
|
|
843
|
+
cache_salts: list[str] | None = None,
|
|
844
|
+
):
|
|
845
|
+
"""
|
|
846
|
+
Submit a batched store request to LMCache
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
request_ids: The IDs of the requests
|
|
850
|
+
ops: The LoadStoreOps describing the store operations. Should have
|
|
851
|
+
the same length as request_ids
|
|
852
|
+
event: The CUDA event that is recorded after the current
|
|
853
|
+
model inference step
|
|
854
|
+
cache_salts: Per-user isolation salts, one per request. If None,
|
|
855
|
+
all requests use cache_salt="". The list length should be the same as
|
|
856
|
+
request_ids.
|
|
857
|
+
"""
|
|
858
|
+
if cache_salts is None:
|
|
859
|
+
cache_salts = [""] * len(request_ids)
|
|
860
|
+
for request_id, op, salt in zip(request_ids, ops, cache_salts, strict=False):
|
|
861
|
+
self.submit_store_request(request_id, op, event, cache_salt=salt)
|
|
862
|
+
|
|
863
|
+
@_lmcache_nvtx_annotate
|
|
864
|
+
def batched_submit_retrieve_requests(
|
|
865
|
+
self,
|
|
866
|
+
request_ids: list[str],
|
|
867
|
+
ops: list[LoadStoreOp],
|
|
868
|
+
event: torch.cuda.Event,
|
|
869
|
+
cache_salts: list[str] | None = None,
|
|
870
|
+
):
|
|
871
|
+
"""
|
|
872
|
+
Submit a batched retrieve request to LMCache
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
request_ids: The IDs of the requests
|
|
876
|
+
ops: The LoadStoreOps describing the retrieve operations. Should have
|
|
877
|
+
the same length as request_ids
|
|
878
|
+
event: The CUDA event that is recorded after the current
|
|
879
|
+
model inference step
|
|
880
|
+
cache_salts: Per-user isolation salts, one per request. If None,
|
|
881
|
+
all requests use cache_salt="". The list length should be same as
|
|
882
|
+
request_ids.
|
|
883
|
+
"""
|
|
884
|
+
if cache_salts is None:
|
|
885
|
+
cache_salts = [""] * len(request_ids)
|
|
886
|
+
for request_id, op, salt in zip(request_ids, ops, cache_salts, strict=False):
|
|
887
|
+
self.submit_retrieve_request(request_id, op, event, cache_salt=salt)
|
|
888
|
+
|
|
889
|
+
def _process_finished_stores(
|
|
890
|
+
self,
|
|
891
|
+
finished_req_ids_from_lmcache: set[str],
|
|
892
|
+
finished_req_ids_from_engine: set[str],
|
|
893
|
+
) -> set[str]:
|
|
894
|
+
"""Merge LMCache-side and engine-side finished store info."""
|
|
895
|
+
self.finished_stores.update(finished_req_ids_from_lmcache)
|
|
896
|
+
ret_stores = set()
|
|
897
|
+
for req_id in finished_req_ids_from_engine:
|
|
898
|
+
if req_id in self._returned_finished:
|
|
899
|
+
continue
|
|
900
|
+
if req_id in self.finished_stores or req_id in self.store_futures:
|
|
901
|
+
self.previously_finished.add(req_id)
|
|
902
|
+
else:
|
|
903
|
+
ret_stores.add(req_id)
|
|
904
|
+
ret_stores.update(self._update_and_get_finished_store())
|
|
905
|
+
self._returned_finished.update(ret_stores)
|
|
906
|
+
return ret_stores
|
|
907
|
+
|
|
908
|
+
@_lmcache_nvtx_annotate
|
|
909
|
+
def get_finished(
|
|
910
|
+
self, finished_req_ids_from_engine: set[str]
|
|
911
|
+
) -> tuple[set[str] | None, set[str] | None]:
|
|
912
|
+
"""
|
|
913
|
+
Check and get the finished store and retrieve requests.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
finished_req_ids_from_engine: the set of request ids that are
|
|
917
|
+
reported as finished from the vLLM engine side.
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
A tuple of two sets:
|
|
921
|
+
- The first set contains the finished store request ids. The returned
|
|
922
|
+
store request ids MUST be seen before in the
|
|
923
|
+
`finished_req_ids_from_engine`.
|
|
924
|
+
- The second set contains the finished retrieve request ids.
|
|
925
|
+
|
|
926
|
+
Notes:
|
|
927
|
+
When enabling async scheduling in vLLM, the same request ID may appear
|
|
928
|
+
multiple times in `finished_req_ids_from_engine`. The adapter should
|
|
929
|
+
take care of deduplicating the request IDs and only return the request
|
|
930
|
+
IDs that have not been returned before.
|
|
931
|
+
"""
|
|
932
|
+
# If unhealthy, drain all pending futures immediately
|
|
933
|
+
if not self.is_healthy:
|
|
934
|
+
finished_stores = set(self.store_futures.keys())
|
|
935
|
+
finished_retrieves = set()
|
|
936
|
+
for request_id, (
|
|
937
|
+
_r_future,
|
|
938
|
+
r_block_ids,
|
|
939
|
+
) in self.retrieve_futures.items():
|
|
940
|
+
finished_retrieves.add(request_id)
|
|
941
|
+
self.error_block_ids.update(r_block_ids)
|
|
942
|
+
self.store_futures.clear()
|
|
943
|
+
self.retrieve_futures.clear()
|
|
944
|
+
|
|
945
|
+
ret_stores = self._process_finished_stores(
|
|
946
|
+
finished_stores, finished_req_ids_from_engine
|
|
947
|
+
)
|
|
948
|
+
# A request may have a pending retrieve AND appear in
|
|
949
|
+
# finished_req_ids_from_engine (it ran without loading KV after
|
|
950
|
+
# the server died). The scheduler processes finished_recving
|
|
951
|
+
# first and deletes the request, so we must not also report it
|
|
952
|
+
# in finished_sending.
|
|
953
|
+
ret_stores -= finished_retrieves
|
|
954
|
+
return ret_stores, finished_retrieves
|
|
955
|
+
|
|
956
|
+
finished_stores = set()
|
|
957
|
+
finished_retrieves = set()
|
|
958
|
+
for request_id, s_future in self.store_futures.items():
|
|
959
|
+
if not s_future.query():
|
|
960
|
+
continue
|
|
961
|
+
|
|
962
|
+
s_result = s_future.result()
|
|
963
|
+
finished_stores.add(request_id)
|
|
964
|
+
|
|
965
|
+
if not s_result:
|
|
966
|
+
logger.error(
|
|
967
|
+
"Something went wrong when processing the "
|
|
968
|
+
"store request for request_id=%s",
|
|
969
|
+
request_id,
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
for request_id, (r_future, _) in self.retrieve_futures.items():
|
|
973
|
+
if not r_future.query():
|
|
974
|
+
continue
|
|
975
|
+
|
|
976
|
+
r_result = r_future.result()
|
|
977
|
+
finished_retrieves.add(request_id)
|
|
978
|
+
|
|
979
|
+
if not r_result:
|
|
980
|
+
logger.error(
|
|
981
|
+
"Something went wrong when processing the "
|
|
982
|
+
"retrieve request for request_id=%s, result=%s",
|
|
983
|
+
request_id,
|
|
984
|
+
r_result,
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
# Remove the finished requests from the tracking dicts
|
|
988
|
+
for request_id in finished_stores:
|
|
989
|
+
self.store_futures.pop(request_id, None)
|
|
990
|
+
for request_id in finished_retrieves:
|
|
991
|
+
self.retrieve_futures.pop(request_id, None)
|
|
992
|
+
|
|
993
|
+
# Update the internal states
|
|
994
|
+
ret_stores = self._process_finished_stores(
|
|
995
|
+
finished_stores, finished_req_ids_from_engine
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
# the invocation of `get_finished` means that
|
|
999
|
+
# these requests' KV caches are already fully stored.
|
|
1000
|
+
# or the requests normally ends without any store.
|
|
1001
|
+
if ret_stores:
|
|
1002
|
+
self.request_telemetry.on_request_store_finished(
|
|
1003
|
+
request_ids_set=ret_stores,
|
|
1004
|
+
model_name=self.model_name,
|
|
1005
|
+
world_size=self.world_size,
|
|
1006
|
+
kv_rank=self.worker_id,
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
return ret_stores, finished_retrieves
|
|
1010
|
+
|
|
1011
|
+
def num_blocks_per_chunk(self) -> int:
|
|
1012
|
+
"""
|
|
1013
|
+
Returns:
|
|
1014
|
+
The number of vllm blocks in a LMCache data chunk
|
|
1015
|
+
"""
|
|
1016
|
+
return self.blocks_in_chunk
|
|
1017
|
+
|
|
1018
|
+
def get_block_ids_with_load_errors(self) -> set[int]:
|
|
1019
|
+
"""
|
|
1020
|
+
Returns the block IDs that failed due to retrieve timeout,
|
|
1021
|
+
then clears the internal set.
|
|
1022
|
+
"""
|
|
1023
|
+
errors = self.error_block_ids.copy()
|
|
1024
|
+
self.error_block_ids.clear()
|
|
1025
|
+
return errors
|
|
1026
|
+
|
|
1027
|
+
def shutdown(self):
|
|
1028
|
+
"""
|
|
1029
|
+
Shutdown the LMCache MP worker adapter
|
|
1030
|
+
"""
|
|
1031
|
+
logger.info("Unregistering kv caches")
|
|
1032
|
+
try:
|
|
1033
|
+
send_lmcache_request(
|
|
1034
|
+
self.mq_client,
|
|
1035
|
+
RequestType.UNREGISTER_KV_CACHE,
|
|
1036
|
+
[self.instance_id],
|
|
1037
|
+
).result(timeout=self._mq_timeout)
|
|
1038
|
+
except TimeoutError:
|
|
1039
|
+
logger.warning(
|
|
1040
|
+
"LMCache server did not respond to unregister within %ss. "
|
|
1041
|
+
"Proceeding with shutdown.",
|
|
1042
|
+
self._mq_timeout,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
self.mq_client.close()
|
|
1046
|
+
self.request_telemetry.close()
|
|
1047
|
+
|
|
1048
|
+
# Helper functions
|
|
1049
|
+
def _update_and_get_finished_store(
|
|
1050
|
+
self,
|
|
1051
|
+
) -> set[str]:
|
|
1052
|
+
"""Converge the internal states about finished stores
|
|
1053
|
+
and returns the 'safe finished store request ids' back
|
|
1054
|
+
"""
|
|
1055
|
+
safe_finished_s = self.finished_stores.intersection(self.previously_finished)
|
|
1056
|
+
self.finished_stores.difference_update(self.previously_finished)
|
|
1057
|
+
self.previously_finished.difference_update(safe_finished_s)
|
|
1058
|
+
|
|
1059
|
+
return safe_finished_s
|
|
1060
|
+
|
|
1061
|
+
def _create_key(
|
|
1062
|
+
self,
|
|
1063
|
+
token_ids: list[int],
|
|
1064
|
+
start: int,
|
|
1065
|
+
end: int,
|
|
1066
|
+
request_id: str,
|
|
1067
|
+
cache_salt: str = "",
|
|
1068
|
+
) -> IPCCacheEngineKey:
|
|
1069
|
+
"""Convert token IDs to an IPC cache engine key.
|
|
1070
|
+
|
|
1071
|
+
Args:
|
|
1072
|
+
token_ids: The token IDs.
|
|
1073
|
+
start: Start token index.
|
|
1074
|
+
end: End token index.
|
|
1075
|
+
request_id: The request ID.
|
|
1076
|
+
cache_salt: Per-user isolation salt.
|
|
1077
|
+
|
|
1078
|
+
Returns:
|
|
1079
|
+
IPCCacheEngineKey: The constructed key.
|
|
1080
|
+
"""
|
|
1081
|
+
return IPCCacheEngineKey(
|
|
1082
|
+
model_name=self.model_name,
|
|
1083
|
+
world_size=self.world_size,
|
|
1084
|
+
worker_id=self.worker_id,
|
|
1085
|
+
token_ids=tuple(token_ids),
|
|
1086
|
+
start=start,
|
|
1087
|
+
end=end,
|
|
1088
|
+
request_id=request_id,
|
|
1089
|
+
cache_salt=cache_salt,
|
|
1090
|
+
)
|