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,407 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
# Third Party
|
|
8
|
+
import msgspec
|
|
9
|
+
import torch
|
|
10
|
+
import zmq
|
|
11
|
+
|
|
12
|
+
# First Party
|
|
13
|
+
from lmcache.logging import init_logger
|
|
14
|
+
from lmcache.v1.cache_engine import LMCacheEngine
|
|
15
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
16
|
+
from lmcache.v1.lookup_client.abstract_client import LookupClientInterface
|
|
17
|
+
from lmcache.v1.lookup_client.async_lookup_message import (
|
|
18
|
+
LookupCleanupMsg,
|
|
19
|
+
LookupRequestMsg,
|
|
20
|
+
LookupResponseMsg,
|
|
21
|
+
)
|
|
22
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
23
|
+
from lmcache.v1.rpc_utils import (
|
|
24
|
+
get_zmq_context,
|
|
25
|
+
get_zmq_rpc_path_lmcache,
|
|
26
|
+
get_zmq_socket,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = init_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# NOTE(Jiayi): Prefetch could load extra redundant cache if multiple
|
|
33
|
+
# workers has different hit tokens.
|
|
34
|
+
class LMCacheAsyncLookupClient(LookupClientInterface):
|
|
35
|
+
"""
|
|
36
|
+
ZMQ-based lookup client that communicates with a lookup server.
|
|
37
|
+
|
|
38
|
+
Related extra_config:
|
|
39
|
+
- lookup_server_worker_ids:
|
|
40
|
+
is a config to control create lookup server on some workers.
|
|
41
|
+
if mla is not enabled, default is [];
|
|
42
|
+
if mla is enabled, default is [0];
|
|
43
|
+
- if lookup_server_worker_ids is [], start lookup server on all workers
|
|
44
|
+
- if lookup_server_worker_ids is [0], start lookup server on worker0
|
|
45
|
+
- if lookup_server_worker_ids is [0, 3, 6], start lookup server on
|
|
46
|
+
worker0, worker3 and worker6
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
config: LMCacheEngineConfig,
|
|
52
|
+
metadata: LMCacheMetadata,
|
|
53
|
+
):
|
|
54
|
+
# lookup_id -> first lookup time
|
|
55
|
+
# this helps us support timeout semantics
|
|
56
|
+
self.first_lookup_time: dict[str, float] = {}
|
|
57
|
+
self.config = config
|
|
58
|
+
|
|
59
|
+
self.ctx = get_zmq_context(use_asyncio=False)
|
|
60
|
+
kv_connector_extra_config = metadata.kv_connector_extra_config or {}
|
|
61
|
+
rpc_port = kv_connector_extra_config.get("lmcache_rpc_port", 0)
|
|
62
|
+
engine_id = metadata.engine_id
|
|
63
|
+
assert engine_id is not None, "engine_id is required for RPC communication"
|
|
64
|
+
self.world_size = metadata.world_size
|
|
65
|
+
self.lookup_server_worker_ids = config.get_lookup_server_worker_ids(
|
|
66
|
+
metadata.use_mla, metadata.world_size
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.push_sockets = []
|
|
70
|
+
if len(self.lookup_server_worker_ids) > 0:
|
|
71
|
+
ranks = self.lookup_server_worker_ids
|
|
72
|
+
self.world_size = len(self.lookup_server_worker_ids)
|
|
73
|
+
else:
|
|
74
|
+
ranks = [i for i in range(self.world_size)]
|
|
75
|
+
|
|
76
|
+
for rank in ranks:
|
|
77
|
+
worker_socket_path = get_zmq_rpc_path_lmcache(
|
|
78
|
+
engine_id, "lookup_worker", rpc_port, rank
|
|
79
|
+
)
|
|
80
|
+
logger.info(
|
|
81
|
+
"lmcache lookup client connect to rank %s with worker socket path %s",
|
|
82
|
+
rank,
|
|
83
|
+
worker_socket_path,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
push_socket = get_zmq_socket(
|
|
87
|
+
self.ctx,
|
|
88
|
+
worker_socket_path,
|
|
89
|
+
"ipc",
|
|
90
|
+
zmq.PUSH, # type: ignore[attr-defined]
|
|
91
|
+
"connect",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.push_sockets.append(push_socket)
|
|
95
|
+
|
|
96
|
+
scheduler_socket_path = get_zmq_rpc_path_lmcache(
|
|
97
|
+
engine_id, "lookup_scheduler", rpc_port, 0
|
|
98
|
+
)
|
|
99
|
+
self.pull_socket = get_zmq_socket(
|
|
100
|
+
self.ctx,
|
|
101
|
+
scheduler_socket_path,
|
|
102
|
+
"ipc",
|
|
103
|
+
zmq.PULL, # type: ignore[attr-defined]
|
|
104
|
+
"bind",
|
|
105
|
+
)
|
|
106
|
+
logger.info(
|
|
107
|
+
"lmcache lookup client connect to scheduler with socket path %s",
|
|
108
|
+
scheduler_socket_path,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# First Party
|
|
112
|
+
from lmcache.v1.token_database import (
|
|
113
|
+
ChunkedTokenDatabase,
|
|
114
|
+
SegmentTokenDatabase,
|
|
115
|
+
TokenDatabase,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.token_database: TokenDatabase
|
|
119
|
+
if config.enable_blending:
|
|
120
|
+
self.token_database = SegmentTokenDatabase(config, metadata)
|
|
121
|
+
else:
|
|
122
|
+
self.token_database = ChunkedTokenDatabase(config, metadata)
|
|
123
|
+
|
|
124
|
+
# A lock is needed since we need another thread to pull
|
|
125
|
+
# responses from the lookup_and_prefetch server
|
|
126
|
+
# (e.g., worker process).
|
|
127
|
+
self.lock = threading.Lock()
|
|
128
|
+
|
|
129
|
+
# map from lookup_id (i.e., req_id) to req's status.
|
|
130
|
+
# None indicates ongoing.
|
|
131
|
+
# int indicates number of hit tokens.
|
|
132
|
+
self.reqs_status: dict[str, Optional[int]] = {}
|
|
133
|
+
|
|
134
|
+
# map from lookup_id (i.e., req_id) to number of hit tokens for each worker
|
|
135
|
+
self.res_for_each_worker: dict[str, list[int]] = {}
|
|
136
|
+
|
|
137
|
+
# The two parts are [lookup_id (i.e., req_id), num_hit_tokens]
|
|
138
|
+
self.num_parts = 2
|
|
139
|
+
|
|
140
|
+
# Track lookup_ids that have been aborted for cleanup
|
|
141
|
+
self.aborted_lookups: set[str] = set()
|
|
142
|
+
|
|
143
|
+
self.running = True
|
|
144
|
+
|
|
145
|
+
self.thread = threading.Thread(
|
|
146
|
+
target=self.process_responses_from_workers,
|
|
147
|
+
daemon=True,
|
|
148
|
+
name="async-lookup-client-thread",
|
|
149
|
+
)
|
|
150
|
+
self.thread.start()
|
|
151
|
+
|
|
152
|
+
# default backoff time
|
|
153
|
+
self.lookup_backoff_time = 0.01
|
|
154
|
+
if config.extra_config is not None:
|
|
155
|
+
self.lookup_backoff_time = float(
|
|
156
|
+
config.extra_config.get("lookup_backoff_time", self.lookup_backoff_time)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def lookup_cache(self, lookup_id: str) -> Optional[int]:
|
|
160
|
+
"""
|
|
161
|
+
-1 means not found;
|
|
162
|
+
None means ongoing;
|
|
163
|
+
int >= 0 means number of hit tokens
|
|
164
|
+
"""
|
|
165
|
+
# Check if any aborted lookups are finished, send cleanup messages
|
|
166
|
+
self._cleanup_finished_aborted_lookups()
|
|
167
|
+
|
|
168
|
+
with self.lock:
|
|
169
|
+
if (req_status := self.reqs_status.get(lookup_id, -1)) == -1:
|
|
170
|
+
self.reqs_status[lookup_id] = None
|
|
171
|
+
self.first_lookup_time[lookup_id] = time.time()
|
|
172
|
+
elif req_status is None:
|
|
173
|
+
time.sleep(self.lookup_backoff_time)
|
|
174
|
+
if (
|
|
175
|
+
time.time() - self.first_lookup_time[lookup_id]
|
|
176
|
+
) * 1000 > self.config.lookup_timeout_ms:
|
|
177
|
+
logger.warning(
|
|
178
|
+
(
|
|
179
|
+
"Request %s is still waiting for async lookup "
|
|
180
|
+
"after %d seconds, returning 0 lmcache cached tokens "
|
|
181
|
+
"so vllm can recompute"
|
|
182
|
+
),
|
|
183
|
+
lookup_id,
|
|
184
|
+
self.config.lookup_timeout_ms // 1000,
|
|
185
|
+
)
|
|
186
|
+
self.cancel_lookup(lookup_id)
|
|
187
|
+
self.first_lookup_time.pop(lookup_id, None)
|
|
188
|
+
return 0
|
|
189
|
+
|
|
190
|
+
return req_status
|
|
191
|
+
|
|
192
|
+
# TODO(Jiayi): Consider batching here
|
|
193
|
+
def lookup(
|
|
194
|
+
self,
|
|
195
|
+
token_ids: Union[torch.Tensor, list[int]],
|
|
196
|
+
lookup_id: str,
|
|
197
|
+
request_configs: Optional[dict] = None,
|
|
198
|
+
) -> Optional[int]:
|
|
199
|
+
hashes: list[int] = []
|
|
200
|
+
offsets = []
|
|
201
|
+
for start, end, hash_val in self.token_database.process_tokens(
|
|
202
|
+
token_ids, make_key=False
|
|
203
|
+
):
|
|
204
|
+
hashes.append(hash_val) # type: ignore[arg-type]
|
|
205
|
+
offsets.append(end - start)
|
|
206
|
+
|
|
207
|
+
# Create structured message
|
|
208
|
+
msg = LookupRequestMsg(
|
|
209
|
+
lookup_id=lookup_id,
|
|
210
|
+
hashes=hashes,
|
|
211
|
+
offsets=offsets,
|
|
212
|
+
request_configs=request_configs,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Serialize message using msgspec
|
|
216
|
+
msg_buf = msgspec.msgpack.encode(msg)
|
|
217
|
+
|
|
218
|
+
for i in range(self.world_size):
|
|
219
|
+
self.push_sockets[i].send(msg_buf, copy=False)
|
|
220
|
+
time.sleep(self.lookup_backoff_time)
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
def process_responses_from_workers(self):
|
|
224
|
+
while self.running:
|
|
225
|
+
try:
|
|
226
|
+
msg_buf = self.pull_socket.recv(copy=False)
|
|
227
|
+
# Deserialize message using msgspec
|
|
228
|
+
msg = msgspec.msgpack.decode(msg_buf, type=LookupResponseMsg)
|
|
229
|
+
lookup_id = msg.lookup_id
|
|
230
|
+
res = msg.num_hit_tokens
|
|
231
|
+
|
|
232
|
+
with self.lock:
|
|
233
|
+
if lookup_id not in self.res_for_each_worker:
|
|
234
|
+
self.res_for_each_worker[lookup_id] = [res]
|
|
235
|
+
else:
|
|
236
|
+
self.res_for_each_worker[lookup_id].append(res)
|
|
237
|
+
all_res = self.res_for_each_worker[lookup_id]
|
|
238
|
+
|
|
239
|
+
if len(all_res) == self.world_size:
|
|
240
|
+
self.res_for_each_worker.pop(lookup_id)
|
|
241
|
+
|
|
242
|
+
# NOTE: it is possible that the number of hit
|
|
243
|
+
# tokens is different across (TP and PP) ranks, so we
|
|
244
|
+
# can use the minimum value as the number of
|
|
245
|
+
# hit tokens.
|
|
246
|
+
self.reqs_status[lookup_id] = min(all_res)
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.error("Error processing response from worker: %s", e)
|
|
250
|
+
|
|
251
|
+
def clear_lookup_status(self, lookup_id: str) -> None:
|
|
252
|
+
with self.lock:
|
|
253
|
+
self.reqs_status.pop(lookup_id, None)
|
|
254
|
+
self.first_lookup_time.pop(lookup_id, None)
|
|
255
|
+
|
|
256
|
+
def cancel_lookup(self, lookup_id: str) -> None:
|
|
257
|
+
"""Mark lookup as aborted. Cleanup will happen after task finishes."""
|
|
258
|
+
self.aborted_lookups.add(lookup_id)
|
|
259
|
+
|
|
260
|
+
def _cleanup_finished_aborted_lookups(self) -> None:
|
|
261
|
+
"""Check for finished aborted lookups and send cleanup messages to workers."""
|
|
262
|
+
# A lookup whose status is None is still loading.
|
|
263
|
+
# We wait for it to finish before cleanup.
|
|
264
|
+
finished_lookups = [
|
|
265
|
+
lookup_id
|
|
266
|
+
for lookup_id in self.aborted_lookups
|
|
267
|
+
if self.reqs_status.get(lookup_id) is not None
|
|
268
|
+
]
|
|
269
|
+
if finished_lookups:
|
|
270
|
+
self.aborted_lookups.difference_update(finished_lookups)
|
|
271
|
+
|
|
272
|
+
# Tell the server to free the reserved memory buffers for each aborted lookup.
|
|
273
|
+
for lookup_id in finished_lookups:
|
|
274
|
+
self._send_cleanup_message(lookup_id)
|
|
275
|
+
self.clear_lookup_status(lookup_id)
|
|
276
|
+
|
|
277
|
+
def _send_cleanup_message(self, lookup_id: str) -> None:
|
|
278
|
+
"""Send cleanup message to workers to release memory objects."""
|
|
279
|
+
msg = LookupCleanupMsg(lookup_id=lookup_id)
|
|
280
|
+
msg_buf = msgspec.msgpack.encode(msg)
|
|
281
|
+
|
|
282
|
+
for i in range(self.world_size):
|
|
283
|
+
self.push_sockets[i].send(msg_buf, copy=False)
|
|
284
|
+
logger.debug("Sent cleanup message for lookup_id=%s", lookup_id)
|
|
285
|
+
|
|
286
|
+
def supports_producer_reuse(self) -> bool:
|
|
287
|
+
"""Return True as LMCacheLookupClient supports producer kvcache reuse"""
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
def close(self):
|
|
291
|
+
self.running = False
|
|
292
|
+
try:
|
|
293
|
+
if self.thread.is_alive():
|
|
294
|
+
self.thread.join(timeout=1.0)
|
|
295
|
+
for s in self.push_sockets:
|
|
296
|
+
s.close(linger=0) # type: ignore[arg-type]
|
|
297
|
+
self.pull_socket.close(linger=0) # type: ignore[arg-type]
|
|
298
|
+
self.ctx.term()
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.warning("Failed to join thread during close: %s", e)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class LMCacheAsyncLookupServer:
|
|
304
|
+
"""ZMQ-based async lookup server that handles lookup and prefetch
|
|
305
|
+
requests using LMCacheEngine."""
|
|
306
|
+
|
|
307
|
+
def __init__(
|
|
308
|
+
self,
|
|
309
|
+
lmcache_engine: LMCacheEngine,
|
|
310
|
+
metadata: LMCacheMetadata,
|
|
311
|
+
):
|
|
312
|
+
self.ctx = zmq.Context() # type: ignore[attr-defined]
|
|
313
|
+
kv_connector_extra_config = metadata.kv_connector_extra_config or {}
|
|
314
|
+
rpc_port = kv_connector_extra_config.get("lmcache_rpc_port", 0)
|
|
315
|
+
assert metadata.engine_id is not None, (
|
|
316
|
+
"engine_id is required for RPC communication"
|
|
317
|
+
)
|
|
318
|
+
worker_socket_path = get_zmq_rpc_path_lmcache(
|
|
319
|
+
metadata.engine_id, "lookup_worker", rpc_port, metadata.worker_id
|
|
320
|
+
)
|
|
321
|
+
scheduler_socket_path = get_zmq_rpc_path_lmcache(
|
|
322
|
+
metadata.engine_id, "lookup_scheduler", rpc_port, 0
|
|
323
|
+
)
|
|
324
|
+
self.push_socket = get_zmq_socket(
|
|
325
|
+
self.ctx,
|
|
326
|
+
scheduler_socket_path,
|
|
327
|
+
"ipc",
|
|
328
|
+
zmq.PUSH, # type: ignore[attr-defined]
|
|
329
|
+
"connect",
|
|
330
|
+
)
|
|
331
|
+
self.pull_socket = get_zmq_socket(
|
|
332
|
+
self.ctx,
|
|
333
|
+
worker_socket_path,
|
|
334
|
+
"ipc",
|
|
335
|
+
zmq.PULL, # type: ignore[attr-defined]
|
|
336
|
+
"bind",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
self.lmcache_engine = lmcache_engine
|
|
340
|
+
self.running = True
|
|
341
|
+
|
|
342
|
+
logger.info(
|
|
343
|
+
"lmcache lookup server start with"
|
|
344
|
+
" scheduler socket path %s, "
|
|
345
|
+
"worker socket path %s",
|
|
346
|
+
scheduler_socket_path,
|
|
347
|
+
worker_socket_path,
|
|
348
|
+
)
|
|
349
|
+
self.thread = threading.Thread(
|
|
350
|
+
target=self.process_requests_from_scheduler,
|
|
351
|
+
daemon=True,
|
|
352
|
+
name="async-lookup-server-thread",
|
|
353
|
+
)
|
|
354
|
+
self.thread.start()
|
|
355
|
+
|
|
356
|
+
def process_requests_from_scheduler(self):
|
|
357
|
+
while self.running:
|
|
358
|
+
try:
|
|
359
|
+
msg_buf = self.pull_socket.recv(copy=False)
|
|
360
|
+
# rely on msgspec to automatically discriminate
|
|
361
|
+
# between LookupRequestMsg and LookupCleanupMsg
|
|
362
|
+
msg = msgspec.msgpack.decode(
|
|
363
|
+
msg_buf,
|
|
364
|
+
type=Union[LookupRequestMsg, LookupCleanupMsg],
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if isinstance(msg, LookupRequestMsg):
|
|
368
|
+
# Handle lookup request
|
|
369
|
+
self.lmcache_engine.async_lookup_and_prefetch(
|
|
370
|
+
lookup_id=msg.lookup_id,
|
|
371
|
+
hashes=msg.hashes,
|
|
372
|
+
offsets=msg.offsets,
|
|
373
|
+
pin=True,
|
|
374
|
+
request_configs=msg.request_configs,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
elif isinstance(msg, LookupCleanupMsg):
|
|
378
|
+
# Handle cleanup request - release memory objects for aborted lookup
|
|
379
|
+
self.lmcache_engine.cleanup_memory_objs(msg.lookup_id)
|
|
380
|
+
|
|
381
|
+
else:
|
|
382
|
+
logger.warning("Unknown message type: %s", type(msg))
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.error("Error processing request from scheduler: %s", e)
|
|
386
|
+
|
|
387
|
+
def send_response_to_scheduler(self, lookup_id: str, num_hit_tokens: int):
|
|
388
|
+
# Create structured response message
|
|
389
|
+
msg = LookupResponseMsg(
|
|
390
|
+
lookup_id=lookup_id,
|
|
391
|
+
num_hit_tokens=num_hit_tokens,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Serialize message using msgspec
|
|
395
|
+
msg_buf = msgspec.msgpack.encode(msg)
|
|
396
|
+
self.push_socket.send(msg_buf, copy=False)
|
|
397
|
+
|
|
398
|
+
def close(self):
|
|
399
|
+
self.running = False
|
|
400
|
+
try:
|
|
401
|
+
if self.thread.is_alive():
|
|
402
|
+
self.thread.join(timeout=1.0)
|
|
403
|
+
self.push_socket.close(linger=0) # type: ignore[arg-type]
|
|
404
|
+
self.pull_socket.close(linger=0) # type: ignore[arg-type]
|
|
405
|
+
self.ctx.term()
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.warning("Failed to join thread during close: %s", e)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
# Third Party
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
# First Party
|
|
11
|
+
from lmcache.logging import init_logger
|
|
12
|
+
from lmcache.v1.cache_engine import LMCacheEngine
|
|
13
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
14
|
+
from lmcache.v1.lookup_client.abstract_client import LookupClientInterface
|
|
15
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
16
|
+
from lmcache.v1.rpc.transport import (
|
|
17
|
+
RpcClientTransport,
|
|
18
|
+
RpcServerTransport,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = init_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LMCacheLookupClient(LookupClientInterface):
|
|
25
|
+
"""
|
|
26
|
+
Lookup client that communicates with a lookup server
|
|
27
|
+
via an injected RpcClientTransport.
|
|
28
|
+
|
|
29
|
+
The client is decoupled from the underlying communication
|
|
30
|
+
mechanism. The transport layer handles connection management,
|
|
31
|
+
retries, and error recovery.
|
|
32
|
+
|
|
33
|
+
Related extra_config:
|
|
34
|
+
- lookup_server_worker_ids:
|
|
35
|
+
is a config to control create lookup server on some
|
|
36
|
+
workers.
|
|
37
|
+
if mla is not enabled, default is [];
|
|
38
|
+
if mla is enabled, default is [0];
|
|
39
|
+
- if lookup_server_worker_ids is [], start lookup
|
|
40
|
+
server on all workers
|
|
41
|
+
- if lookup_server_worker_ids is [0], start lookup
|
|
42
|
+
server on worker0
|
|
43
|
+
- if lookup_server_worker_ids is [0, 3, 6], start
|
|
44
|
+
lookup server on worker0, worker3 and worker6
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
config: LMCacheEngineConfig,
|
|
50
|
+
metadata: LMCacheMetadata,
|
|
51
|
+
transport: RpcClientTransport,
|
|
52
|
+
):
|
|
53
|
+
self.config = config
|
|
54
|
+
self.transport = transport
|
|
55
|
+
|
|
56
|
+
# NOTE: map from lookup_id (i.e., req_id) to
|
|
57
|
+
# req's status.
|
|
58
|
+
# int indicates number of hit tokens.
|
|
59
|
+
# The assumption here is that once a request is
|
|
60
|
+
# looked up, the following lookups of the same
|
|
61
|
+
# request must have the same result.
|
|
62
|
+
self.reqs_status: dict[str, int] = {}
|
|
63
|
+
|
|
64
|
+
# First Party
|
|
65
|
+
from lmcache.v1.token_database import (
|
|
66
|
+
ChunkedTokenDatabase,
|
|
67
|
+
SegmentTokenDatabase,
|
|
68
|
+
TokenDatabase,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.enable_blending = config.enable_blending
|
|
72
|
+
self.token_database: TokenDatabase
|
|
73
|
+
if self.enable_blending:
|
|
74
|
+
self.token_database = SegmentTokenDatabase(config, metadata)
|
|
75
|
+
else:
|
|
76
|
+
self.token_database = ChunkedTokenDatabase(config, metadata)
|
|
77
|
+
|
|
78
|
+
def lookup_cache(self, lookup_id: str) -> Optional[int]:
|
|
79
|
+
"""
|
|
80
|
+
"-1 means not found;
|
|
81
|
+
None means ongoing; (not supported in sync client)
|
|
82
|
+
int >= 0 means number of hit tokens
|
|
83
|
+
"""
|
|
84
|
+
return self.reqs_status.get(lookup_id, -1)
|
|
85
|
+
|
|
86
|
+
def lookup(
|
|
87
|
+
self,
|
|
88
|
+
token_ids: Union[torch.Tensor, list[int]],
|
|
89
|
+
lookup_id: str,
|
|
90
|
+
request_configs: Optional[dict] = None,
|
|
91
|
+
) -> Optional[int]:
|
|
92
|
+
request_configs_str = ""
|
|
93
|
+
if request_configs is not None and len(request_configs) != 0:
|
|
94
|
+
request_configs_str = json.dumps(request_configs)
|
|
95
|
+
|
|
96
|
+
# NOTE(Jiayi): We cannot only send hashes when
|
|
97
|
+
# blending enabled because the blender need the
|
|
98
|
+
# input embedding.
|
|
99
|
+
if not self.enable_blending:
|
|
100
|
+
hashes = []
|
|
101
|
+
offsets = []
|
|
102
|
+
|
|
103
|
+
for (
|
|
104
|
+
start,
|
|
105
|
+
end,
|
|
106
|
+
key,
|
|
107
|
+
) in self.token_database.process_tokens(token_ids, make_key=False):
|
|
108
|
+
hashes.append(key)
|
|
109
|
+
offsets.append(end - start)
|
|
110
|
+
|
|
111
|
+
# if the token database returns no hashes,
|
|
112
|
+
# return 0
|
|
113
|
+
if not hashes:
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
msg_buf = [
|
|
117
|
+
hashes,
|
|
118
|
+
offsets,
|
|
119
|
+
lookup_id,
|
|
120
|
+
request_configs_str,
|
|
121
|
+
]
|
|
122
|
+
else:
|
|
123
|
+
msg_buf = [
|
|
124
|
+
token_ids,
|
|
125
|
+
lookup_id,
|
|
126
|
+
request_configs_str,
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
responses = self.transport.send_and_recv_all(msg_buf)
|
|
130
|
+
|
|
131
|
+
# Transport returns empty list on failure
|
|
132
|
+
if not responses:
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
results = [int.from_bytes(resp, "big") for resp in responses]
|
|
136
|
+
|
|
137
|
+
assert len(results) == self.transport.world_size
|
|
138
|
+
if len(set(results)) > 1:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"Lookup results (number of hit tokens) "
|
|
141
|
+
"differ across (TP and PP) ranks: %s.",
|
|
142
|
+
results,
|
|
143
|
+
)
|
|
144
|
+
# NOTE: it is possible that the number of hit
|
|
145
|
+
# tokens is different across (TP and PP) ranks,
|
|
146
|
+
# so we can use the minimum value.
|
|
147
|
+
num_hit_toks = min(results)
|
|
148
|
+
self.reqs_status[lookup_id] = num_hit_toks
|
|
149
|
+
|
|
150
|
+
return num_hit_toks
|
|
151
|
+
|
|
152
|
+
def clear_lookup_status(self, lookup_id: str) -> None:
|
|
153
|
+
self.reqs_status.pop(lookup_id, None)
|
|
154
|
+
|
|
155
|
+
def supports_producer_reuse(self) -> bool:
|
|
156
|
+
"""Return True as LMCacheLookupClient supports
|
|
157
|
+
producer kvcache reuse"""
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def __enter__(self):
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
164
|
+
self.close()
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def close(self):
|
|
168
|
+
self.transport.close()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class LMCacheLookupServer:
|
|
172
|
+
"""Lookup server that handles lookup requests using
|
|
173
|
+
LMCacheEngine, with an injected RpcServerTransport."""
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
lmcache_engine: LMCacheEngine,
|
|
178
|
+
metadata: LMCacheMetadata,
|
|
179
|
+
transport: RpcServerTransport,
|
|
180
|
+
):
|
|
181
|
+
self.transport = transport
|
|
182
|
+
self.lmcache_engine = lmcache_engine
|
|
183
|
+
self.running = True
|
|
184
|
+
self.enable_blending = lmcache_engine.config.enable_blending
|
|
185
|
+
|
|
186
|
+
def process_request():
|
|
187
|
+
while self.running:
|
|
188
|
+
try:
|
|
189
|
+
result = self.transport.recv_request()
|
|
190
|
+
if result is None:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
identity, data_frames = result
|
|
194
|
+
|
|
195
|
+
# Validate frame structure
|
|
196
|
+
if len(data_frames) < 3:
|
|
197
|
+
logger.warning("Malformed request received: not enough frames.")
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# Validate and decode lookup_id
|
|
201
|
+
lookup_id_bytes = data_frames[-2]
|
|
202
|
+
request_configs_bytes = data_frames[-1]
|
|
203
|
+
|
|
204
|
+
if not isinstance(lookup_id_bytes, (bytes, str)):
|
|
205
|
+
logger.warning(
|
|
206
|
+
"Malformed request received: lookup_id is not bytes or str."
|
|
207
|
+
)
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
if not isinstance(request_configs_bytes, (bytes, str)):
|
|
211
|
+
logger.warning(
|
|
212
|
+
"Malformed request received: "
|
|
213
|
+
"request_configs is not bytes or str."
|
|
214
|
+
)
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# Decode to strings
|
|
218
|
+
if isinstance(lookup_id_bytes, bytes):
|
|
219
|
+
lookup_id = lookup_id_bytes.decode("utf-8")
|
|
220
|
+
else:
|
|
221
|
+
lookup_id = lookup_id_bytes
|
|
222
|
+
|
|
223
|
+
if isinstance(request_configs_bytes, bytes):
|
|
224
|
+
request_configs_str = request_configs_bytes.decode("utf-8")
|
|
225
|
+
else:
|
|
226
|
+
request_configs_str = request_configs_bytes
|
|
227
|
+
|
|
228
|
+
request_configs = (
|
|
229
|
+
json.loads(request_configs_str) if request_configs_str else None
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if not self.enable_blending:
|
|
233
|
+
hashes = data_frames[0]
|
|
234
|
+
offsets = data_frames[1]
|
|
235
|
+
lookup_result = self.lmcache_engine.lookup(
|
|
236
|
+
hashes=hashes,
|
|
237
|
+
offsets=offsets,
|
|
238
|
+
lookup_id=lookup_id,
|
|
239
|
+
pin=True,
|
|
240
|
+
request_configs=request_configs,
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
tokens = data_frames[0]
|
|
244
|
+
lookup_result = self.lmcache_engine.lookup(
|
|
245
|
+
tokens=tokens,
|
|
246
|
+
lookup_id=lookup_id,
|
|
247
|
+
pin=True,
|
|
248
|
+
request_configs=request_configs,
|
|
249
|
+
)
|
|
250
|
+
response = lookup_result.to_bytes(4, "big")
|
|
251
|
+
self.transport.send_response(identity, response)
|
|
252
|
+
except json.JSONDecodeError as e:
|
|
253
|
+
logger.error(f"Error decoding JSON in lookup request: {e}")
|
|
254
|
+
except UnicodeDecodeError as e:
|
|
255
|
+
logger.error(f"Error decoding UTF-8 in lookup request: {e}")
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(f"Error processing lookup request: {e}")
|
|
258
|
+
|
|
259
|
+
logger.info("lmcache lookup server started")
|
|
260
|
+
self.thread = threading.Thread(
|
|
261
|
+
target=process_request,
|
|
262
|
+
daemon=True,
|
|
263
|
+
name="lookup-server-thread",
|
|
264
|
+
)
|
|
265
|
+
self.thread.start()
|
|
266
|
+
|
|
267
|
+
def __enter__(self):
|
|
268
|
+
return self
|
|
269
|
+
|
|
270
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
271
|
+
self.close()
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
def close(self):
|
|
275
|
+
# Stop the processing thread first
|
|
276
|
+
self.running = False
|
|
277
|
+
|
|
278
|
+
# Wait for thread to finish with timeout
|
|
279
|
+
if self.thread.is_alive():
|
|
280
|
+
self.thread.join(timeout=2.0)
|
|
281
|
+
if self.thread.is_alive():
|
|
282
|
+
logger.warning("Lookup server thread did not terminate gracefully")
|
|
283
|
+
|
|
284
|
+
# Close transport after thread is stopped
|
|
285
|
+
self.transport.close()
|