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,1134 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import partial
|
|
5
|
+
from itertools import islice
|
|
6
|
+
from typing import Generator
|
|
7
|
+
import argparse
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
# Third Party
|
|
12
|
+
import torch
|
|
13
|
+
import zmq
|
|
14
|
+
|
|
15
|
+
# First Party
|
|
16
|
+
from lmcache.logging import init_logger
|
|
17
|
+
from lmcache.utils import _lmcache_nvtx_annotate
|
|
18
|
+
from lmcache.v1.distributed.api import (
|
|
19
|
+
MemoryLayoutDesc,
|
|
20
|
+
ObjectKey,
|
|
21
|
+
ipc_key_to_object_keys,
|
|
22
|
+
)
|
|
23
|
+
from lmcache.v1.distributed.config import (
|
|
24
|
+
StorageManagerConfig,
|
|
25
|
+
add_storage_manager_args,
|
|
26
|
+
parse_args_to_config,
|
|
27
|
+
)
|
|
28
|
+
from lmcache.v1.distributed.storage_manager import PrefetchHandle, StorageManager
|
|
29
|
+
from lmcache.v1.gpu_connector.gpu_ops import (
|
|
30
|
+
lmcache_memcpy_async_d2h,
|
|
31
|
+
lmcache_memcpy_async_h2d,
|
|
32
|
+
)
|
|
33
|
+
from lmcache.v1.gpu_connector.utils import LayoutHints
|
|
34
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
35
|
+
from lmcache.v1.mp_observability.config import (
|
|
36
|
+
ObservabilityConfig,
|
|
37
|
+
add_observability_args,
|
|
38
|
+
init_observability,
|
|
39
|
+
parse_args_to_observability_config,
|
|
40
|
+
)
|
|
41
|
+
from lmcache.v1.mp_observability.event import Event, EventType
|
|
42
|
+
from lmcache.v1.mp_observability.event_bus import get_event_bus
|
|
43
|
+
from lmcache.v1.mp_observability.otel_init import register_gauge
|
|
44
|
+
from lmcache.v1.mp_observability.trace import maybe_initialize_trace_recorder
|
|
45
|
+
from lmcache.v1.multiprocess.config import (
|
|
46
|
+
MPServerConfig,
|
|
47
|
+
add_mp_server_args,
|
|
48
|
+
parse_args_to_mp_server_config,
|
|
49
|
+
)
|
|
50
|
+
from lmcache.v1.multiprocess.custom_types import (
|
|
51
|
+
BlockAllocationRecord,
|
|
52
|
+
IPCCacheEngineKey,
|
|
53
|
+
KVCache,
|
|
54
|
+
)
|
|
55
|
+
from lmcache.v1.multiprocess.gpu_context import (
|
|
56
|
+
GPUCacheContext,
|
|
57
|
+
)
|
|
58
|
+
from lmcache.v1.multiprocess.mq import MessageQueueServer
|
|
59
|
+
from lmcache.v1.multiprocess.protocol import (
|
|
60
|
+
RequestType,
|
|
61
|
+
get_handler_type,
|
|
62
|
+
get_payload_classes,
|
|
63
|
+
)
|
|
64
|
+
from lmcache.v1.multiprocess.session import SessionManager
|
|
65
|
+
from lmcache.v1.multiprocess.token_hasher import TokenHasher
|
|
66
|
+
import lmcache.c_ops as lmc_ops
|
|
67
|
+
|
|
68
|
+
logger = init_logger(__name__)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Helper functions
|
|
72
|
+
def compute_extra_count(
|
|
73
|
+
tp_size: int,
|
|
74
|
+
world_size: int,
|
|
75
|
+
) -> int:
|
|
76
|
+
"""Compute extra count for MLA multi-reader locking.
|
|
77
|
+
|
|
78
|
+
Non-MLA: each TP worker owns a distinct KV shard,
|
|
79
|
+
so each ObjectKey is retrieved by exactly 1
|
|
80
|
+
worker -> extra_count = 0.
|
|
81
|
+
MLA: TP does not split KV caches, all TP workers
|
|
82
|
+
share the same object. vLLM passes world_size
|
|
83
|
+
already divided by tp_size (e.g. world_size=1
|
|
84
|
+
for TP=4 PP=1), so ipc_keys_to_object_keys
|
|
85
|
+
only produces 1 ObjectKey per chunk. All TP
|
|
86
|
+
workers retrieve that same ObjectKey, hence
|
|
87
|
+
extra_count = tp_size - 1.
|
|
88
|
+
|
|
89
|
+
Detection: tp > world_size means MLA (world_size
|
|
90
|
+
was divided by tp on the vLLM side).
|
|
91
|
+
|
|
92
|
+
Fallback: old vLLM (<= 0.8.5) does not send
|
|
93
|
+
tp_size (defaults to 1); we fall back to
|
|
94
|
+
world_size which gives extra_count = 0
|
|
95
|
+
(safe but may under-lock for MLA).
|
|
96
|
+
|
|
97
|
+
TODO: world_size currently carries an overloaded
|
|
98
|
+
meaning (total ranks for non-MLA vs total/tp for
|
|
99
|
+
MLA). Consider a dedicated field in the future.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
tp_size: Tensor-parallel size from the client.
|
|
103
|
+
world_size: World size from the cache key.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Number of extra count (0 for non-MLA).
|
|
107
|
+
"""
|
|
108
|
+
tp = tp_size if tp_size > 1 else world_size
|
|
109
|
+
return tp - 1 if tp > world_size else 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_layout_desc(gpu_context: GPUCacheContext, num_tokens: int) -> MemoryLayoutDesc:
|
|
113
|
+
"""Get the memory layout description for a given GPU context and number of tokens.
|
|
114
|
+
|
|
115
|
+
Supports multiple KV layer groups with different shapes and dtypes.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
gpu_context: The GPU cache context containing the KV cache information.
|
|
119
|
+
num_tokens: The number of tokens to determine the layout for.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
MemoryLayoutDesc: The memory layout description containing shapes and dtypes.
|
|
123
|
+
"""
|
|
124
|
+
num_groups = gpu_context.kv_layer_groups_manager.num_groups
|
|
125
|
+
shapes = [
|
|
126
|
+
gpu_context.get_kv_buffer_shape(num_tokens, group_idx)
|
|
127
|
+
for group_idx in range(num_groups)
|
|
128
|
+
]
|
|
129
|
+
dtypes = [
|
|
130
|
+
gpu_context.kv_layer_groups_manager.kv_layer_groups[group_idx].dtype
|
|
131
|
+
for group_idx in range(num_groups)
|
|
132
|
+
]
|
|
133
|
+
return MemoryLayoutDesc(shapes=shapes, dtypes=dtypes)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def batched_iteration(lst: list, batch_size: int) -> Generator[tuple, None, None]:
|
|
137
|
+
"""Utility function to iterate over a list in batches.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
lst: The list to iterate over.
|
|
141
|
+
batch_size: The size of each batch.
|
|
142
|
+
|
|
143
|
+
Yields:
|
|
144
|
+
Batches of the list as tuples.
|
|
145
|
+
"""
|
|
146
|
+
if batch_size < 1:
|
|
147
|
+
raise ValueError("batch size must be at least one")
|
|
148
|
+
it = iter(lst)
|
|
149
|
+
while batch := tuple(islice(it, batch_size)):
|
|
150
|
+
yield batch
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class _PrefetchJob:
|
|
155
|
+
handle: PrefetchHandle
|
|
156
|
+
world_size: int
|
|
157
|
+
request_id: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Main class for the mp cache engine
|
|
161
|
+
class MPCacheEngine:
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
storage_manager_config: StorageManagerConfig,
|
|
165
|
+
chunk_size: int = 256,
|
|
166
|
+
hash_algorithm: str = "blake3",
|
|
167
|
+
):
|
|
168
|
+
# GPU ID -> KV cache tensors
|
|
169
|
+
self.gpu_contexts: dict[int, GPUCacheContext] = {}
|
|
170
|
+
|
|
171
|
+
# GPU ID -> (model name, world size) as metadata
|
|
172
|
+
# NOTE: This is mainly for determining the layout desc during prefetch
|
|
173
|
+
# We assume that if the (model name, world size) is the same, then
|
|
174
|
+
# the layout desc returned by the gpu context is the same.
|
|
175
|
+
self.gpu_context_meta: dict[int, tuple[str, int]] = {}
|
|
176
|
+
|
|
177
|
+
# chunk size
|
|
178
|
+
self.chunk_size = chunk_size
|
|
179
|
+
|
|
180
|
+
# Lock for clear() to avoid concurrent storage manager mutations
|
|
181
|
+
self.lock = threading.Lock()
|
|
182
|
+
|
|
183
|
+
# storage manager
|
|
184
|
+
self.storage_manager = StorageManager(storage_manager_config)
|
|
185
|
+
|
|
186
|
+
# Token hasher and session manager for token-based operations
|
|
187
|
+
self.token_hasher = TokenHasher(
|
|
188
|
+
chunk_size=chunk_size, hash_algorithm=hash_algorithm
|
|
189
|
+
)
|
|
190
|
+
self.session_manager = SessionManager(self.token_hasher)
|
|
191
|
+
|
|
192
|
+
# EventBus for observability
|
|
193
|
+
self._event_bus = get_event_bus()
|
|
194
|
+
|
|
195
|
+
# Prefetch job tracking for two-phase lookup, keyed by request_id.
|
|
196
|
+
# TODO: implement periodic cleanup of stale _prefetch_jobs entries
|
|
197
|
+
# for crash resilience (e.g., client calls lookup but never queries)
|
|
198
|
+
self._prefetch_jobs: dict[str, _PrefetchJob] = {}
|
|
199
|
+
self._prefetch_job_lock = threading.Lock()
|
|
200
|
+
|
|
201
|
+
self._setup_metrics()
|
|
202
|
+
|
|
203
|
+
def register_kv_cache(
|
|
204
|
+
self,
|
|
205
|
+
instance_id: int,
|
|
206
|
+
kv_caches: KVCache,
|
|
207
|
+
model_name: str,
|
|
208
|
+
world_size: int,
|
|
209
|
+
layout_hints: LayoutHints,
|
|
210
|
+
) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Registers the KV cache tensors for a given GPU instance ID.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
instance_id (int): The GPU instance ID (such as PID).
|
|
216
|
+
kv_caches (KVCache): The KV cache tensor wrappers from vLLM.
|
|
217
|
+
model_name (str): The name of the model associated with this KV cache.
|
|
218
|
+
world_size (int): The world size associated with this KV cache.
|
|
219
|
+
layout_hints: See :class:`LayoutHints`. Forwarded to
|
|
220
|
+
:class:`GPUCacheContext` for GPU KV format detection.
|
|
221
|
+
"""
|
|
222
|
+
gpu_context = GPUCacheContext(
|
|
223
|
+
kv_caches,
|
|
224
|
+
self.chunk_size,
|
|
225
|
+
layout_hints=layout_hints or None,
|
|
226
|
+
)
|
|
227
|
+
self.gpu_contexts[instance_id] = gpu_context
|
|
228
|
+
self.gpu_context_meta[instance_id] = (model_name, world_size)
|
|
229
|
+
logger.info(
|
|
230
|
+
"Registered KV cache for GPU ID %d with %d layers",
|
|
231
|
+
instance_id,
|
|
232
|
+
gpu_context.num_layers,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def unregister_kv_cache(self, instance_id: int) -> None:
|
|
236
|
+
"""
|
|
237
|
+
Unregisters the KV cache tensors for a given GPU instance ID.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
instance_id (int): The GPU instance ID (such as PID).
|
|
241
|
+
"""
|
|
242
|
+
if instance_id in self.gpu_contexts:
|
|
243
|
+
del self.gpu_contexts[instance_id]
|
|
244
|
+
del self.gpu_context_meta[instance_id]
|
|
245
|
+
logger.info("Unregistered KV cache for GPU ID %d", instance_id)
|
|
246
|
+
torch.cuda.empty_cache()
|
|
247
|
+
else:
|
|
248
|
+
logger.warning("No KV cache found for GPU ID %d to unregister", instance_id)
|
|
249
|
+
|
|
250
|
+
@_lmcache_nvtx_annotate
|
|
251
|
+
def store(
|
|
252
|
+
self,
|
|
253
|
+
key: IPCCacheEngineKey,
|
|
254
|
+
instance_id: int,
|
|
255
|
+
gpu_block_ids: list[int],
|
|
256
|
+
event_ipc_handle: bytes,
|
|
257
|
+
) -> tuple[bytes, bool]:
|
|
258
|
+
"""
|
|
259
|
+
Stores the GPU KV cache blocks to CPU.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
key (IPCCacheEngineKey): The IPC key for the KV cache blocks.
|
|
263
|
+
Must have worker_id != None (worker store operation).
|
|
264
|
+
instance_id (int): The GPU instance ID (such as PID).
|
|
265
|
+
gpu_block_ids (list[int]): The GPU block IDs to store.
|
|
266
|
+
event_ipc_handle (bytes): The IPC handle of the event to wait on.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
tuple[bytes, bool]: The first element is the IPC handle of the event
|
|
270
|
+
that signals the completion of the store operation. The second
|
|
271
|
+
element indicates whether the store operation was successful.
|
|
272
|
+
"""
|
|
273
|
+
session = self.session_manager.get_or_create(key.request_id)
|
|
274
|
+
session.set_tokens(list(key.token_ids))
|
|
275
|
+
chunk_hashes = [
|
|
276
|
+
TokenHasher.hash_to_bytes(h) for h in session.get_hashes(key.start, key.end)
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
st = time.perf_counter()
|
|
280
|
+
|
|
281
|
+
assert key.worker_id is not None, "Must store with worker_id != None"
|
|
282
|
+
obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
|
|
283
|
+
|
|
284
|
+
assert instance_id in self.gpu_contexts, (
|
|
285
|
+
f"KV cache not registered for GPU ID {instance_id}"
|
|
286
|
+
)
|
|
287
|
+
gpu_context = self.gpu_contexts[instance_id]
|
|
288
|
+
|
|
289
|
+
blocks_per_chunk = self.chunk_size // gpu_context.block_size
|
|
290
|
+
|
|
291
|
+
with (
|
|
292
|
+
torch.cuda.device(gpu_context.device),
|
|
293
|
+
torch.cuda.stream(gpu_context.stream),
|
|
294
|
+
):
|
|
295
|
+
event = torch.cuda.Event(interprocess=True)
|
|
296
|
+
|
|
297
|
+
# Stage all block_ids to GPU once before the loop
|
|
298
|
+
all_block_ids_gpu = gpu_context.stage_block_ids(gpu_block_ids)
|
|
299
|
+
|
|
300
|
+
# Wait for vLLM to finish
|
|
301
|
+
vllm_event = torch.cuda.Event.from_ipc_handle(
|
|
302
|
+
gpu_context.device, event_ipc_handle
|
|
303
|
+
)
|
|
304
|
+
vllm_event.wait(stream=gpu_context.stream)
|
|
305
|
+
|
|
306
|
+
# CPU-synchronous sentinel: a GPU store is about to be enqueued.
|
|
307
|
+
# Must be published via publish() (not publish_on_stream) so the
|
|
308
|
+
# drain thread sees it before MP_SESSION_END can race MP_STORE_END.
|
|
309
|
+
self._event_bus.publish(
|
|
310
|
+
Event(
|
|
311
|
+
event_type=EventType.MP_STORE_SUBMITTED,
|
|
312
|
+
session_id=key.request_id,
|
|
313
|
+
metadata={"device": str(gpu_context.device)},
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
self._event_bus.publish_on_stream(
|
|
318
|
+
gpu_context.cupy_stream,
|
|
319
|
+
Event(
|
|
320
|
+
event_type=EventType.MP_STORE_START,
|
|
321
|
+
session_id=key.request_id,
|
|
322
|
+
metadata={"device": str(gpu_context.device)},
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
reserved_dict: dict = {}
|
|
327
|
+
try:
|
|
328
|
+
layout_desc = get_layout_desc(gpu_context, self.chunk_size)
|
|
329
|
+
reserved_dict = self.storage_manager.reserve_write(
|
|
330
|
+
obj_keys, layout_desc, "new"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# NOTE: Store is not batched because some obj_keys may be
|
|
334
|
+
# skipped (not in reserved_dict), making block_ids
|
|
335
|
+
# non-contiguous. Batching would require torch.cat to
|
|
336
|
+
# reassemble block_ids, negating the benefit.
|
|
337
|
+
num_groups = gpu_context.kv_layer_groups_manager.num_groups
|
|
338
|
+
for idx, obj_key in enumerate(obj_keys):
|
|
339
|
+
if obj_key in reserved_dict:
|
|
340
|
+
memory_obj = reserved_dict[obj_key]
|
|
341
|
+
else:
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
chunk_block_ids_gpu = all_block_ids_gpu[
|
|
345
|
+
idx * blocks_per_chunk : (idx + 1) * blocks_per_chunk
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
# Copy from GPU paged buffer to tmp buffer, then to CPU — per group
|
|
349
|
+
for group_idx in range(num_groups):
|
|
350
|
+
tmp_buffer = gpu_context.get_tmp_chunk_gpu_buffer(group_idx)
|
|
351
|
+
group_kv_pointers = gpu_context.get_group_kv_pointers(group_idx)
|
|
352
|
+
lmc_ops.multi_layer_block_kv_transfer(
|
|
353
|
+
group_kv_pointers,
|
|
354
|
+
[tmp_buffer.data_ptr()],
|
|
355
|
+
chunk_block_ids_gpu,
|
|
356
|
+
gpu_context.device,
|
|
357
|
+
lmc_ops.TransferDirection.D2H,
|
|
358
|
+
gpu_context.get_shape_desc(group_idx),
|
|
359
|
+
self.chunk_size,
|
|
360
|
+
gpu_context.gpu_kv_format_,
|
|
361
|
+
0,
|
|
362
|
+
)
|
|
363
|
+
# Store is not batched, so we always use chunk_idx=0 (single slot)
|
|
364
|
+
lmcache_memcpy_async_d2h(
|
|
365
|
+
gpu_context.get_tmp_gpu_buffer_flat(chunk_idx=0), memory_obj
|
|
366
|
+
)
|
|
367
|
+
except Exception:
|
|
368
|
+
logger.exception("Cannot store keys due to exception")
|
|
369
|
+
finally:
|
|
370
|
+
event.record()
|
|
371
|
+
if reserved_dict:
|
|
372
|
+
gpu_context.cupy_stream.launch_host_func(
|
|
373
|
+
self.storage_manager.finish_write,
|
|
374
|
+
list(reserved_dict.keys()),
|
|
375
|
+
)
|
|
376
|
+
self._event_bus.publish_on_stream(
|
|
377
|
+
gpu_context.cupy_stream,
|
|
378
|
+
Event(
|
|
379
|
+
event_type=EventType.MP_STORE_END,
|
|
380
|
+
session_id=key.request_id,
|
|
381
|
+
metadata={
|
|
382
|
+
"stored_count": len(reserved_dict),
|
|
383
|
+
"device": str(gpu_context.device),
|
|
384
|
+
},
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
ed = time.perf_counter()
|
|
389
|
+
if length := len(reserved_dict):
|
|
390
|
+
logger.info(
|
|
391
|
+
"Stored %d tokens in %.3f seconds",
|
|
392
|
+
length * self.chunk_size,
|
|
393
|
+
ed - st,
|
|
394
|
+
)
|
|
395
|
+
return event.ipc_handle(), True
|
|
396
|
+
|
|
397
|
+
@_lmcache_nvtx_annotate
|
|
398
|
+
def retrieve(
|
|
399
|
+
self,
|
|
400
|
+
key: IPCCacheEngineKey,
|
|
401
|
+
instance_id: int,
|
|
402
|
+
gpu_block_ids: list[int],
|
|
403
|
+
event_ipc_handle: bytes,
|
|
404
|
+
skip_first_n_tokens: int = 0,
|
|
405
|
+
) -> tuple[bytes, bool]:
|
|
406
|
+
"""
|
|
407
|
+
Retrieves the CPU KV cache and put into GPU blocks.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
key (IPCCacheEngineKey): The IPC key for the KV cache blocks.
|
|
411
|
+
Must have worker_id != None (worker retrieve operation).
|
|
412
|
+
instance_id (int): The GPU instance ID (such as PID).
|
|
413
|
+
gpu_block_ids (list[int]): The GPU block IDs to retrieve into.
|
|
414
|
+
event_ipc_handle (bytes): The IPC handle of the event to wait on.
|
|
415
|
+
skip_first_n_tokens (int): Number of tokens to skip writing at
|
|
416
|
+
the start of the retrieve range. This avoids overwriting
|
|
417
|
+
APC-shared GPU blocks that may be read concurrently by other
|
|
418
|
+
requests.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
tuple[bytes, bool]: The first element is the IPC handle of the event
|
|
422
|
+
that signals the completion of the retrieve operation. The second
|
|
423
|
+
element indicates whether the key was successfully retrieved.
|
|
424
|
+
"""
|
|
425
|
+
session = self.session_manager.get_or_create(key.request_id)
|
|
426
|
+
session.set_tokens(list(key.token_ids))
|
|
427
|
+
chunk_hashes = [
|
|
428
|
+
TokenHasher.hash_to_bytes(h) for h in session.get_hashes(key.start, key.end)
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
st = time.perf_counter()
|
|
432
|
+
|
|
433
|
+
assert key.worker_id is not None, "Must retrieve with worker_id != None"
|
|
434
|
+
obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
|
|
435
|
+
|
|
436
|
+
assert instance_id in self.gpu_contexts, (
|
|
437
|
+
f"KV cache not registered for GPU ID {instance_id}"
|
|
438
|
+
)
|
|
439
|
+
gpu_context = self.gpu_contexts[instance_id]
|
|
440
|
+
|
|
441
|
+
# CPU-synchronous sentinel: a GPU retrieve is about to be enqueued.
|
|
442
|
+
# Must be published via publish() (not publish_on_stream) so the
|
|
443
|
+
# drain thread sees it before MP_SESSION_END can race MP_RETRIEVE_END.
|
|
444
|
+
self._event_bus.publish(
|
|
445
|
+
Event(
|
|
446
|
+
event_type=EventType.MP_RETRIEVE_SUBMITTED,
|
|
447
|
+
session_id=key.request_id,
|
|
448
|
+
metadata={"device": str(gpu_context.device)},
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
self._event_bus.publish_on_stream(
|
|
453
|
+
gpu_context.cupy_stream,
|
|
454
|
+
Event(
|
|
455
|
+
event_type=EventType.MP_RETRIEVE_START,
|
|
456
|
+
session_id=key.request_id,
|
|
457
|
+
metadata={"device": str(gpu_context.device)},
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
blocks_per_chunk = self.chunk_size // gpu_context.block_size
|
|
462
|
+
|
|
463
|
+
def _retrieve_loop(keys: list[ObjectKey], memory_objs: list[MemoryObj]) -> None:
|
|
464
|
+
_BATCH_SIZE = gpu_context.max_batch_size
|
|
465
|
+
num_groups = gpu_context.kv_layer_groups_manager.num_groups
|
|
466
|
+
for batch_idx, memory_obj_batch in enumerate(
|
|
467
|
+
batched_iteration(memory_objs, batch_size=_BATCH_SIZE)
|
|
468
|
+
):
|
|
469
|
+
batch_len = len(memory_obj_batch)
|
|
470
|
+
chunk_start = batch_idx * self.chunk_size * _BATCH_SIZE
|
|
471
|
+
chunk_end = chunk_start + self.chunk_size * batch_len
|
|
472
|
+
|
|
473
|
+
effective_start = max(chunk_start, skip_first_n_tokens)
|
|
474
|
+
if effective_start >= chunk_end:
|
|
475
|
+
# Entire batch is within APC range, skip it
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
skip_tokens_in_chunk = max(
|
|
479
|
+
0,
|
|
480
|
+
min(
|
|
481
|
+
effective_start - chunk_start,
|
|
482
|
+
self.chunk_size * batch_len - 1,
|
|
483
|
+
),
|
|
484
|
+
)
|
|
485
|
+
if skip_tokens_in_chunk % gpu_context.block_size != 0:
|
|
486
|
+
logger.error(
|
|
487
|
+
"skip_first_n_tokens (%d) is not aligned to block_size (%d), "
|
|
488
|
+
"rounding down from %d tokens to %d blocks",
|
|
489
|
+
skip_first_n_tokens,
|
|
490
|
+
gpu_context.block_size,
|
|
491
|
+
skip_tokens_in_chunk,
|
|
492
|
+
skip_tokens_in_chunk // gpu_context.block_size,
|
|
493
|
+
)
|
|
494
|
+
skip_blocks_in_chunk = skip_tokens_in_chunk // gpu_context.block_size
|
|
495
|
+
|
|
496
|
+
start_chunk_id = batch_idx * _BATCH_SIZE
|
|
497
|
+
end_chunk_id = start_chunk_id + batch_len
|
|
498
|
+
chunk_block_ids_gpu = all_block_ids_gpu[
|
|
499
|
+
start_chunk_id * blocks_per_chunk : end_chunk_id * blocks_per_chunk
|
|
500
|
+
]
|
|
501
|
+
|
|
502
|
+
# Copy from CPU to GPU tmp buffers, then scatter to paged KV — per group
|
|
503
|
+
# H2D copy: each memory_obj maps to its own batch slot
|
|
504
|
+
for chunk_idx, memory_obj in enumerate(memory_obj_batch):
|
|
505
|
+
lmcache_memcpy_async_h2d(
|
|
506
|
+
memory_obj,
|
|
507
|
+
gpu_context.get_tmp_gpu_buffer_flat(chunk_idx=chunk_idx),
|
|
508
|
+
)
|
|
509
|
+
for group_idx in range(num_groups):
|
|
510
|
+
tmp_buffers = gpu_context.get_tmp_chunk_gpu_buffer_batched(
|
|
511
|
+
batch_len, group_idx
|
|
512
|
+
)
|
|
513
|
+
group_kv_pointers = gpu_context.get_group_kv_pointers(group_idx)
|
|
514
|
+
|
|
515
|
+
lmc_ops.multi_layer_block_kv_transfer(
|
|
516
|
+
group_kv_pointers,
|
|
517
|
+
[tb.data_ptr() for tb in tmp_buffers],
|
|
518
|
+
chunk_block_ids_gpu,
|
|
519
|
+
gpu_context.device,
|
|
520
|
+
lmc_ops.TransferDirection.H2D,
|
|
521
|
+
gpu_context.get_shape_desc(group_idx),
|
|
522
|
+
self.chunk_size,
|
|
523
|
+
gpu_context.gpu_kv_format_,
|
|
524
|
+
skip_blocks_in_chunk,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
with (
|
|
528
|
+
torch.cuda.device(gpu_context.device),
|
|
529
|
+
torch.cuda.stream(gpu_context.stream),
|
|
530
|
+
):
|
|
531
|
+
# Stage all block_ids to GPU once before the loop
|
|
532
|
+
all_block_ids_gpu = gpu_context.stage_block_ids(gpu_block_ids)
|
|
533
|
+
|
|
534
|
+
event = torch.cuda.Event(interprocess=True)
|
|
535
|
+
|
|
536
|
+
prefetched_keys: list[ObjectKey] = []
|
|
537
|
+
retrieve_succeeded = False
|
|
538
|
+
try:
|
|
539
|
+
with self.storage_manager.read_prefetched_results(
|
|
540
|
+
obj_keys
|
|
541
|
+
) as memory_objs:
|
|
542
|
+
if not memory_objs or len(memory_objs) != len(obj_keys):
|
|
543
|
+
logger.error("Some keys not found during retrieve!")
|
|
544
|
+
return event.ipc_handle(), False
|
|
545
|
+
|
|
546
|
+
prefetched_keys = obj_keys[: len(memory_objs)]
|
|
547
|
+
_retrieve_loop(obj_keys, memory_objs)
|
|
548
|
+
# Only set True when with-block exits normally
|
|
549
|
+
retrieve_succeeded = True
|
|
550
|
+
except Exception:
|
|
551
|
+
logger.exception("Cannot retrieve keys due to exception")
|
|
552
|
+
return event.ipc_handle(), False
|
|
553
|
+
finally:
|
|
554
|
+
event.record()
|
|
555
|
+
if retrieve_succeeded:
|
|
556
|
+
gpu_context.cupy_stream.launch_host_func(
|
|
557
|
+
self.storage_manager.finish_read_prefetched,
|
|
558
|
+
prefetched_keys,
|
|
559
|
+
)
|
|
560
|
+
self._event_bus.publish_on_stream(
|
|
561
|
+
gpu_context.cupy_stream,
|
|
562
|
+
Event(
|
|
563
|
+
event_type=EventType.MP_RETRIEVE_END,
|
|
564
|
+
session_id=key.request_id,
|
|
565
|
+
metadata={
|
|
566
|
+
"retrieved_count": len(prefetched_keys),
|
|
567
|
+
"device": str(gpu_context.device),
|
|
568
|
+
},
|
|
569
|
+
),
|
|
570
|
+
)
|
|
571
|
+
tokens_retrieved = len(obj_keys) * self.chunk_size
|
|
572
|
+
ed = time.perf_counter()
|
|
573
|
+
logger.info(
|
|
574
|
+
"Retrieved %d tokens in %.3f seconds",
|
|
575
|
+
tokens_retrieved,
|
|
576
|
+
ed - st,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return event.ipc_handle(), True
|
|
580
|
+
|
|
581
|
+
def _find_layout_desc(
|
|
582
|
+
self,
|
|
583
|
+
model_name: str,
|
|
584
|
+
world_size: int,
|
|
585
|
+
) -> MemoryLayoutDesc | None:
|
|
586
|
+
"""Find layout desc from a matching GPU context.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
The layout descriptor, or None if no context
|
|
590
|
+
matches (model_name, world_size).
|
|
591
|
+
"""
|
|
592
|
+
for gpu_id, (m, w) in self.gpu_context_meta.items():
|
|
593
|
+
if m == model_name and w == world_size:
|
|
594
|
+
return get_layout_desc(
|
|
595
|
+
self.gpu_contexts[gpu_id],
|
|
596
|
+
self.chunk_size,
|
|
597
|
+
)
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
def lookup(
|
|
601
|
+
self,
|
|
602
|
+
key: IPCCacheEngineKey,
|
|
603
|
+
tp_size: int,
|
|
604
|
+
) -> None:
|
|
605
|
+
"""Submit a prefix lookup.
|
|
606
|
+
|
|
607
|
+
Hashes the key, submits a prefetch task to the storage manager,
|
|
608
|
+
and registers the job under ``key.request_id`` for later polling
|
|
609
|
+
via query_prefetch_status.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
key: Cache key with request_id embedded.
|
|
613
|
+
tp_size: Tensor-parallel size for MLA multi-reader locking.
|
|
614
|
+
"""
|
|
615
|
+
model_name, world_size = key.model_name, key.world_size
|
|
616
|
+
self._event_bus.publish(
|
|
617
|
+
Event(
|
|
618
|
+
event_type=EventType.MP_REQUEST_START,
|
|
619
|
+
session_id=key.request_id,
|
|
620
|
+
)
|
|
621
|
+
)
|
|
622
|
+
self._event_bus.publish(
|
|
623
|
+
Event(
|
|
624
|
+
event_type=EventType.MP_LOOKUP_PREFETCH_START,
|
|
625
|
+
session_id=key.request_id,
|
|
626
|
+
)
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
layout_desc = self._find_layout_desc(model_name, world_size)
|
|
630
|
+
if layout_desc is None:
|
|
631
|
+
logger.error(
|
|
632
|
+
"No GPU context found for model %s with world size %d during lookup!",
|
|
633
|
+
model_name,
|
|
634
|
+
world_size,
|
|
635
|
+
)
|
|
636
|
+
self._register_prefetch_job(
|
|
637
|
+
_PrefetchJob(
|
|
638
|
+
handle=PrefetchHandle(
|
|
639
|
+
prefetch_request_id=-1,
|
|
640
|
+
external_request_id=key.request_id,
|
|
641
|
+
l1_prefix_hit_count=0,
|
|
642
|
+
total_requested_keys=0,
|
|
643
|
+
submit_time=time.monotonic(),
|
|
644
|
+
),
|
|
645
|
+
world_size=1,
|
|
646
|
+
request_id=key.request_id,
|
|
647
|
+
)
|
|
648
|
+
)
|
|
649
|
+
return
|
|
650
|
+
|
|
651
|
+
extra_count = compute_extra_count(tp_size, world_size)
|
|
652
|
+
|
|
653
|
+
# Compute chunk hashes for all full chunks
|
|
654
|
+
chunk_hashes = self.token_hasher.compute_chunk_hashes(list(key.token_ids))
|
|
655
|
+
if not chunk_hashes:
|
|
656
|
+
self._register_prefetch_job(
|
|
657
|
+
_PrefetchJob(
|
|
658
|
+
handle=PrefetchHandle(
|
|
659
|
+
prefetch_request_id=-1,
|
|
660
|
+
external_request_id=key.request_id,
|
|
661
|
+
l1_prefix_hit_count=0,
|
|
662
|
+
total_requested_keys=0,
|
|
663
|
+
submit_time=time.monotonic(),
|
|
664
|
+
),
|
|
665
|
+
world_size=1,
|
|
666
|
+
request_id=key.request_id,
|
|
667
|
+
)
|
|
668
|
+
)
|
|
669
|
+
return
|
|
670
|
+
|
|
671
|
+
# Publish lookup event via EventBus for observability subscribers.
|
|
672
|
+
# Guard with has_subscribers() to avoid allocating the metadata dict
|
|
673
|
+
# (including dtype/shape list comprehensions) when no subscriber is
|
|
674
|
+
# listening (e.g. lookup hash logger is disabled).
|
|
675
|
+
if self._event_bus.has_subscribers(EventType.MP_LOOKUP):
|
|
676
|
+
self._event_bus.publish(
|
|
677
|
+
Event(
|
|
678
|
+
event_type=EventType.MP_LOOKUP,
|
|
679
|
+
session_id=key.request_id,
|
|
680
|
+
metadata={
|
|
681
|
+
"request_id": key.request_id,
|
|
682
|
+
"chunk_hashes": chunk_hashes,
|
|
683
|
+
"model_name": model_name,
|
|
684
|
+
"chunk_size": self.chunk_size,
|
|
685
|
+
"seq_len": len(key.token_ids),
|
|
686
|
+
"dtypes": [str(d) for d in layout_desc.dtypes],
|
|
687
|
+
"shapes": [list(s) for s in layout_desc.shapes],
|
|
688
|
+
},
|
|
689
|
+
)
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
# set lookup ipc key, for session manager to use and generate object keys
|
|
693
|
+
session = self.session_manager.get_or_create(key.request_id)
|
|
694
|
+
session.set_tokens(list(key.token_ids))
|
|
695
|
+
session.lookup_ipc_key = key
|
|
696
|
+
|
|
697
|
+
obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
|
|
698
|
+
|
|
699
|
+
handle = self.storage_manager.submit_prefetch_task(
|
|
700
|
+
obj_keys,
|
|
701
|
+
layout_desc,
|
|
702
|
+
extra_count=extra_count,
|
|
703
|
+
external_request_id=key.request_id,
|
|
704
|
+
)
|
|
705
|
+
self._register_prefetch_job(
|
|
706
|
+
_PrefetchJob(
|
|
707
|
+
handle=handle,
|
|
708
|
+
world_size=key.world_size,
|
|
709
|
+
request_id=key.request_id,
|
|
710
|
+
)
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
def _register_prefetch_job(self, job: _PrefetchJob) -> None:
|
|
714
|
+
with self._prefetch_job_lock:
|
|
715
|
+
self._prefetch_jobs[job.request_id] = job
|
|
716
|
+
|
|
717
|
+
def query_prefetch_lookup_hits(
|
|
718
|
+
self,
|
|
719
|
+
request_id: str,
|
|
720
|
+
) -> int | None:
|
|
721
|
+
"""Query the number of hits for a prefetch request before it's finished.
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
The number of hits for the prefetched keys if the lookup phase is
|
|
725
|
+
done. None if the lookup phase is still in progress. 0 if the
|
|
726
|
+
request_id is unknown (already completed and consumed, or invalid).
|
|
727
|
+
"""
|
|
728
|
+
with self._prefetch_job_lock:
|
|
729
|
+
job = self._prefetch_jobs.get(request_id)
|
|
730
|
+
|
|
731
|
+
if job is None:
|
|
732
|
+
logger.warning(
|
|
733
|
+
"Prefetch job for request %s not found (already completed or invalid)",
|
|
734
|
+
request_id,
|
|
735
|
+
)
|
|
736
|
+
return 0
|
|
737
|
+
|
|
738
|
+
found_count = self.storage_manager.query_prefetch_lookup_hits(job.handle)
|
|
739
|
+
if found_count is None:
|
|
740
|
+
return None
|
|
741
|
+
|
|
742
|
+
found_count = found_count // job.world_size
|
|
743
|
+
return found_count
|
|
744
|
+
|
|
745
|
+
def query_prefetch_status(
|
|
746
|
+
self,
|
|
747
|
+
request_id: str,
|
|
748
|
+
) -> int | None:
|
|
749
|
+
"""Poll the status of a prefetch job by request_id.
|
|
750
|
+
|
|
751
|
+
Returns the chunk count when the prefetch is complete, or None
|
|
752
|
+
if it is still in progress. The job entry is automatically
|
|
753
|
+
removed once a non-None result is returned (exactly-once
|
|
754
|
+
semantics).
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
request_id: The external request ID passed in the lookup key.
|
|
758
|
+
|
|
759
|
+
Returns:
|
|
760
|
+
Chunk count (int) when done, None if still in progress,
|
|
761
|
+
0 if the request_id is unknown (already completed and consumed,
|
|
762
|
+
or invalid).
|
|
763
|
+
"""
|
|
764
|
+
with self._prefetch_job_lock:
|
|
765
|
+
job = self._prefetch_jobs.get(request_id)
|
|
766
|
+
if job is None:
|
|
767
|
+
logger.warning(
|
|
768
|
+
"Prefetch job for request %s not found (already completed or invalid)",
|
|
769
|
+
request_id,
|
|
770
|
+
)
|
|
771
|
+
return 0
|
|
772
|
+
|
|
773
|
+
found_count = self.storage_manager.query_prefetch_status(job.handle)
|
|
774
|
+
if found_count is None:
|
|
775
|
+
return None
|
|
776
|
+
|
|
777
|
+
# NOTE(Kuntai): this assumes two things:
|
|
778
|
+
# 1. the world size is the same between keys
|
|
779
|
+
# 2. the lookup sort the keys in prefix order and breaks at the
|
|
780
|
+
# first failure
|
|
781
|
+
found_count = found_count // job.world_size
|
|
782
|
+
|
|
783
|
+
self._event_bus.publish(
|
|
784
|
+
Event(
|
|
785
|
+
event_type=EventType.MP_LOOKUP_PREFETCH_END,
|
|
786
|
+
session_id=job.request_id,
|
|
787
|
+
metadata={"found_count": found_count},
|
|
788
|
+
)
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
with self._prefetch_job_lock:
|
|
792
|
+
self._prefetch_jobs.pop(request_id, None)
|
|
793
|
+
|
|
794
|
+
return found_count
|
|
795
|
+
|
|
796
|
+
def free_lookup_locks(
|
|
797
|
+
self,
|
|
798
|
+
key: IPCCacheEngineKey,
|
|
799
|
+
tp_size: int,
|
|
800
|
+
) -> None:
|
|
801
|
+
"""Release read locks acquired during lookup.
|
|
802
|
+
|
|
803
|
+
Hashes are computed only for chunks in ``[start, end)`` to avoid
|
|
804
|
+
unnecessary work on tokens outside that range.
|
|
805
|
+
``start`` and ``end`` must be aligned to ``chunk_size``; it is the
|
|
806
|
+
caller's responsibility to align the boundaries as desired.
|
|
807
|
+
|
|
808
|
+
Computes the extra reader count from ``tp_size`` and
|
|
809
|
+
``world_size`` the same way :meth:`lookup` does, so
|
|
810
|
+
the correct number of locks is released.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
key: Cache key whose read locks should be released.
|
|
814
|
+
tp_size: Tensor-parallel size for MLA
|
|
815
|
+
multi-reader locking.
|
|
816
|
+
"""
|
|
817
|
+
chunk_hashes = self.token_hasher.compute_chunk_hashes(
|
|
818
|
+
list(key.token_ids), start=key.start, end=key.end
|
|
819
|
+
)
|
|
820
|
+
if not chunk_hashes:
|
|
821
|
+
return
|
|
822
|
+
obj_keys = ipc_key_to_object_keys(key, chunk_hashes)
|
|
823
|
+
|
|
824
|
+
extra_count = compute_extra_count(tp_size, key.world_size)
|
|
825
|
+
|
|
826
|
+
self.storage_manager.finish_read_prefetched(obj_keys, extra_count=extra_count)
|
|
827
|
+
|
|
828
|
+
# =========================================================================
|
|
829
|
+
# Utility methods
|
|
830
|
+
# =========================================================================
|
|
831
|
+
|
|
832
|
+
def ping(self) -> bool:
|
|
833
|
+
"""
|
|
834
|
+
Respond to a ping request.
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
bool: Always True.
|
|
838
|
+
"""
|
|
839
|
+
return True
|
|
840
|
+
|
|
841
|
+
def get_chunk_size(self) -> int:
|
|
842
|
+
"""
|
|
843
|
+
Returns the chunk size used for KV cache operations.
|
|
844
|
+
|
|
845
|
+
Returns:
|
|
846
|
+
int: The chunk size.
|
|
847
|
+
"""
|
|
848
|
+
return self.chunk_size
|
|
849
|
+
|
|
850
|
+
def end_session(self, request_id: str) -> None:
|
|
851
|
+
"""Remove the session for a finished request.
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
request_id: The request ID whose session should be removed.
|
|
855
|
+
"""
|
|
856
|
+
self._event_bus.publish(
|
|
857
|
+
Event(
|
|
858
|
+
event_type=EventType.MP_VLLM_END_SESSION,
|
|
859
|
+
metadata={"request_id": request_id},
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
session = self.session_manager.remove(request_id)
|
|
863
|
+
self._event_bus.publish(
|
|
864
|
+
Event(
|
|
865
|
+
event_type=EventType.MP_SESSION_END,
|
|
866
|
+
session_id=request_id,
|
|
867
|
+
)
|
|
868
|
+
)
|
|
869
|
+
if session is None:
|
|
870
|
+
logger.warning("Session %s not found, skipping touch", request_id)
|
|
871
|
+
return
|
|
872
|
+
if session.lookup_ipc_key is None:
|
|
873
|
+
logger.warning(
|
|
874
|
+
"Session %s has no lookup ipc key, skipping touch", request_id
|
|
875
|
+
)
|
|
876
|
+
return
|
|
877
|
+
|
|
878
|
+
chunk_hashes = [TokenHasher.hash_to_bytes(h) for h in session.get_hashes(0)]
|
|
879
|
+
obj_keys = ipc_key_to_object_keys(session.lookup_ipc_key, chunk_hashes)
|
|
880
|
+
# unified touch of all keys, which include retrieved and stored keys
|
|
881
|
+
# TODO(chunxiaozheng): when l2 is enabled, the prefetched keys from l2 are temp
|
|
882
|
+
# and will be deleted after finish_read_prefetched, when we touch all keys,
|
|
883
|
+
# these keys has been deleted and will not be touched.
|
|
884
|
+
self.storage_manager.touch_l1_keys(obj_keys)
|
|
885
|
+
|
|
886
|
+
def report_status(self) -> dict:
|
|
887
|
+
"""Return a status dict for the entire cache engine."""
|
|
888
|
+
sm = self.storage_manager.report_status()
|
|
889
|
+
|
|
890
|
+
gpu_context_meta: dict[str, dict] = {}
|
|
891
|
+
for gpu_id, meta in self.gpu_context_meta.items():
|
|
892
|
+
entry: dict = {
|
|
893
|
+
"model_name": meta[0],
|
|
894
|
+
"world_size": meta[1],
|
|
895
|
+
}
|
|
896
|
+
ctx = self.gpu_contexts.get(gpu_id)
|
|
897
|
+
if ctx is not None:
|
|
898
|
+
entry["kv_cache_layout"] = {
|
|
899
|
+
"num_layers": ctx.num_layers,
|
|
900
|
+
"block_size": ctx.block_size,
|
|
901
|
+
"hidden_dim_sizes": str(ctx.hidden_dim_sizes),
|
|
902
|
+
"dtype": str(ctx.dtype),
|
|
903
|
+
"is_mla": ctx.is_mla,
|
|
904
|
+
"num_blocks": ctx.num_blocks,
|
|
905
|
+
"gpu_kv_format": ctx.gpu_kv_format_name,
|
|
906
|
+
"gpu_kv_shape": ctx.gpu_kv_shape,
|
|
907
|
+
"gpu_kv_concrete_shape": ctx.concrete_gpu_kv_shape,
|
|
908
|
+
"attention_backend": ctx.attention_backend,
|
|
909
|
+
"cache_size_per_token": ctx.cache_size_per_token(),
|
|
910
|
+
}
|
|
911
|
+
gpu_context_meta[str(gpu_id)] = entry
|
|
912
|
+
|
|
913
|
+
return {
|
|
914
|
+
"is_healthy": sm["is_healthy"],
|
|
915
|
+
"engine_type": self.__class__.__name__,
|
|
916
|
+
"chunk_size": self.chunk_size,
|
|
917
|
+
"hash_algorithm": self.token_hasher.hash_algorithm_name,
|
|
918
|
+
"registered_gpu_ids": list(self.gpu_contexts.keys()),
|
|
919
|
+
"gpu_context_meta": gpu_context_meta,
|
|
920
|
+
"active_sessions": self.session_manager.active_count(),
|
|
921
|
+
"active_prefetch_jobs": self._active_prefetch_count(),
|
|
922
|
+
"storage_manager": sm,
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
def report_block_allocations(
|
|
926
|
+
self,
|
|
927
|
+
instance_id: int,
|
|
928
|
+
model_name: str,
|
|
929
|
+
records: list[BlockAllocationRecord],
|
|
930
|
+
) -> None:
|
|
931
|
+
"""Publish vLLM block allocation records to the EventBus.
|
|
932
|
+
|
|
933
|
+
Args:
|
|
934
|
+
instance_id: The scheduler instance ID.
|
|
935
|
+
model_name: The model name from the adapter.
|
|
936
|
+
records: List of BlockAllocationRecord with per-request
|
|
937
|
+
block and token allocation deltas.
|
|
938
|
+
"""
|
|
939
|
+
self._event_bus.publish(
|
|
940
|
+
Event(
|
|
941
|
+
event_type=EventType.MP_VLLM_BLOCK_ALLOCATION,
|
|
942
|
+
metadata={
|
|
943
|
+
"instance_id": instance_id,
|
|
944
|
+
"model_name": model_name,
|
|
945
|
+
"records": records,
|
|
946
|
+
},
|
|
947
|
+
)
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
def debug(self) -> str:
|
|
951
|
+
return "OK"
|
|
952
|
+
|
|
953
|
+
def clear(self) -> None:
|
|
954
|
+
"""
|
|
955
|
+
Clears all stored KV cache data from the storage manager.
|
|
956
|
+
"""
|
|
957
|
+
with self.lock:
|
|
958
|
+
self.storage_manager.memcheck()
|
|
959
|
+
self.storage_manager.clear(force=True)
|
|
960
|
+
self.storage_manager.memcheck()
|
|
961
|
+
|
|
962
|
+
def close(self) -> None:
|
|
963
|
+
"""
|
|
964
|
+
Closes the MPCacheEngine and releases all resources.
|
|
965
|
+
"""
|
|
966
|
+
# Close storage manager
|
|
967
|
+
self.storage_manager.close()
|
|
968
|
+
logger.info("MPCacheEngine closed")
|
|
969
|
+
|
|
970
|
+
# Release GPU contexts
|
|
971
|
+
self.gpu_contexts.clear()
|
|
972
|
+
|
|
973
|
+
def _active_prefetch_count(self) -> int:
|
|
974
|
+
"""Return the number of active prefetch jobs (thread-safe)."""
|
|
975
|
+
with self._prefetch_job_lock:
|
|
976
|
+
return len(self._prefetch_jobs)
|
|
977
|
+
|
|
978
|
+
def _setup_metrics(self) -> None:
|
|
979
|
+
"""Register OTel observable gauges for MP engine metrics."""
|
|
980
|
+
_gauge = partial(register_gauge, "lmcache.mp_engine")
|
|
981
|
+
_gauge(
|
|
982
|
+
"lmcache_mp.active_prefetch_jobs",
|
|
983
|
+
"Number of active prefetch jobs",
|
|
984
|
+
self._active_prefetch_count,
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def add_handler_helper(
|
|
989
|
+
server: MessageQueueServer, request_type: RequestType, handler_function
|
|
990
|
+
):
|
|
991
|
+
payload_classes = get_payload_classes(request_type)
|
|
992
|
+
handler_type = get_handler_type(request_type)
|
|
993
|
+
server.add_handler(
|
|
994
|
+
request_type,
|
|
995
|
+
payload_classes,
|
|
996
|
+
handler_type,
|
|
997
|
+
handler_function,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def run_cache_server(
|
|
1002
|
+
mp_config: MPServerConfig,
|
|
1003
|
+
storage_manager_config: StorageManagerConfig,
|
|
1004
|
+
obs_config: ObservabilityConfig,
|
|
1005
|
+
return_engine: bool = False,
|
|
1006
|
+
):
|
|
1007
|
+
"""
|
|
1008
|
+
Run the LMCache cache server with ZMQ message queue.
|
|
1009
|
+
|
|
1010
|
+
Args:
|
|
1011
|
+
mp_config: Configuration for the ZMQ multiprocess server
|
|
1012
|
+
storage_manager_config: Configuration for the storage manager
|
|
1013
|
+
obs_config: Configuration for the observability stack
|
|
1014
|
+
return_engine: If True, return (server, engine) after starting;
|
|
1015
|
+
if False, run blocking loop to keep server alive
|
|
1016
|
+
|
|
1017
|
+
Returns:
|
|
1018
|
+
If return_engine is True: tuple of (MessageQueueServer, MPCacheEngine)
|
|
1019
|
+
If return_engine is False: None (blocks until interrupted)
|
|
1020
|
+
"""
|
|
1021
|
+
event_bus = init_observability(obs_config)
|
|
1022
|
+
|
|
1023
|
+
# Wire up the trace recorder (no-op when --trace-level is unset).
|
|
1024
|
+
# Registered before the engine handlers are added so any
|
|
1025
|
+
# storage-manager calls during engine init are captured too.
|
|
1026
|
+
maybe_initialize_trace_recorder(event_bus, obs_config, storage_manager_config)
|
|
1027
|
+
|
|
1028
|
+
# Initialize the engine (loggers self-register with the global controller)
|
|
1029
|
+
engine = MPCacheEngine(
|
|
1030
|
+
storage_manager_config=storage_manager_config,
|
|
1031
|
+
chunk_size=mp_config.chunk_size,
|
|
1032
|
+
hash_algorithm=mp_config.hash_algorithm,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
# Initialize the message queue server
|
|
1036
|
+
context = zmq.Context.instance()
|
|
1037
|
+
server = MessageQueueServer(
|
|
1038
|
+
bind_url=f"tcp://{mp_config.host}:{mp_config.port}",
|
|
1039
|
+
context=context,
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
# Add handlers
|
|
1043
|
+
add_handler_helper(server, RequestType.REGISTER_KV_CACHE, engine.register_kv_cache)
|
|
1044
|
+
add_handler_helper(
|
|
1045
|
+
server, RequestType.UNREGISTER_KV_CACHE, engine.unregister_kv_cache
|
|
1046
|
+
)
|
|
1047
|
+
add_handler_helper(server, RequestType.STORE, engine.store)
|
|
1048
|
+
add_handler_helper(server, RequestType.LOOKUP, engine.lookup)
|
|
1049
|
+
add_handler_helper(
|
|
1050
|
+
server, RequestType.QUERY_PREFETCH_STATUS, engine.query_prefetch_status
|
|
1051
|
+
)
|
|
1052
|
+
add_handler_helper(
|
|
1053
|
+
server,
|
|
1054
|
+
RequestType.QUERY_PREFETCH_LOOKUP_HITS,
|
|
1055
|
+
engine.query_prefetch_lookup_hits,
|
|
1056
|
+
)
|
|
1057
|
+
add_handler_helper(server, RequestType.FREE_LOOKUP_LOCKS, engine.free_lookup_locks)
|
|
1058
|
+
add_handler_helper(server, RequestType.RETRIEVE, engine.retrieve)
|
|
1059
|
+
add_handler_helper(server, RequestType.CLEAR, engine.clear)
|
|
1060
|
+
add_handler_helper(server, RequestType.GET_CHUNK_SIZE, engine.get_chunk_size)
|
|
1061
|
+
add_handler_helper(server, RequestType.PING, engine.ping)
|
|
1062
|
+
add_handler_helper(server, RequestType.END_SESSION, engine.end_session)
|
|
1063
|
+
add_handler_helper(server, RequestType.NOOP, engine.debug)
|
|
1064
|
+
add_handler_helper(
|
|
1065
|
+
server,
|
|
1066
|
+
RequestType.REPORT_BLOCK_ALLOCATION,
|
|
1067
|
+
engine.report_block_allocations,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
# Assign thread pools
|
|
1071
|
+
server.add_affinity_thread_pool(
|
|
1072
|
+
[RequestType.STORE, RequestType.RETRIEVE],
|
|
1073
|
+
max_workers=mp_config.max_gpu_workers,
|
|
1074
|
+
)
|
|
1075
|
+
server.add_normal_thread_pool(
|
|
1076
|
+
[
|
|
1077
|
+
RequestType.LOOKUP,
|
|
1078
|
+
RequestType.QUERY_PREFETCH_STATUS,
|
|
1079
|
+
RequestType.QUERY_PREFETCH_LOOKUP_HITS,
|
|
1080
|
+
RequestType.FREE_LOOKUP_LOCKS,
|
|
1081
|
+
RequestType.END_SESSION,
|
|
1082
|
+
RequestType.CLEAR,
|
|
1083
|
+
RequestType.PING,
|
|
1084
|
+
RequestType.REPORT_BLOCK_ALLOCATION,
|
|
1085
|
+
],
|
|
1086
|
+
max_workers=mp_config.max_cpu_workers,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
logger.info(
|
|
1090
|
+
"LMCache ZMQ cache server is running on tcp://%s:%d",
|
|
1091
|
+
mp_config.host,
|
|
1092
|
+
mp_config.port,
|
|
1093
|
+
)
|
|
1094
|
+
# Start the ZMQ server
|
|
1095
|
+
torch.cuda.init()
|
|
1096
|
+
server.start()
|
|
1097
|
+
|
|
1098
|
+
logger.info("LMCache cache server is running...")
|
|
1099
|
+
|
|
1100
|
+
# Return server and engine if requested (for HTTP server integration)
|
|
1101
|
+
if return_engine:
|
|
1102
|
+
return server, engine
|
|
1103
|
+
|
|
1104
|
+
# Dummy loop to keep the server running
|
|
1105
|
+
try:
|
|
1106
|
+
while True:
|
|
1107
|
+
time.sleep(1)
|
|
1108
|
+
except KeyboardInterrupt:
|
|
1109
|
+
logger.info("Shutting down server...")
|
|
1110
|
+
event_bus.stop()
|
|
1111
|
+
server.close()
|
|
1112
|
+
engine.close()
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def parse_args():
|
|
1116
|
+
parser = argparse.ArgumentParser(
|
|
1117
|
+
description="LMCache ZMQ Cache Server (without HTTP)"
|
|
1118
|
+
)
|
|
1119
|
+
add_mp_server_args(parser)
|
|
1120
|
+
add_storage_manager_args(parser)
|
|
1121
|
+
add_observability_args(parser)
|
|
1122
|
+
return parser.parse_args()
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
if __name__ == "__main__":
|
|
1126
|
+
args = parse_args()
|
|
1127
|
+
mp_config = parse_args_to_mp_server_config(args)
|
|
1128
|
+
storage_manager_config = parse_args_to_config(args)
|
|
1129
|
+
obs_config = parse_args_to_observability_config(args)
|
|
1130
|
+
run_cache_server(
|
|
1131
|
+
mp_config=mp_config,
|
|
1132
|
+
storage_manager_config=storage_manager_config,
|
|
1133
|
+
obs_config=obs_config,
|
|
1134
|
+
)
|