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,1713 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generator, Optional, Union
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
# Third Party
|
|
9
|
+
from vllm.config import (
|
|
10
|
+
VllmConfig,
|
|
11
|
+
)
|
|
12
|
+
from vllm.distributed.kv_transfer.kv_connector.v1.base import (
|
|
13
|
+
KVConnectorBase_V1,
|
|
14
|
+
KVConnectorMetadata,
|
|
15
|
+
KVConnectorRole,
|
|
16
|
+
)
|
|
17
|
+
from vllm.distributed.parallel_state import (
|
|
18
|
+
get_pp_group,
|
|
19
|
+
)
|
|
20
|
+
from vllm.sampling_params import SamplingParams
|
|
21
|
+
from vllm.v1.core.sched.output import SchedulerOutput
|
|
22
|
+
from vllm.v1.request import RequestStatus
|
|
23
|
+
from vllm.version import __version__ as VLLM_VERSION
|
|
24
|
+
import torch
|
|
25
|
+
|
|
26
|
+
# First Party
|
|
27
|
+
# Use LMCache's own math utilities instead of vllm's
|
|
28
|
+
# (avoids dependency on vllm internal changes like https://github.com/vllm-project/vllm/pull/27188)
|
|
29
|
+
from lmcache import utils
|
|
30
|
+
from lmcache.integration.vllm.utils import (
|
|
31
|
+
ENGINE_NAME,
|
|
32
|
+
apply_mm_hashes_to_token_ids,
|
|
33
|
+
extract_mm_features,
|
|
34
|
+
lmcache_get_or_create_config,
|
|
35
|
+
)
|
|
36
|
+
from lmcache.integration.vllm.vllm_service_factory import VllmServiceFactory
|
|
37
|
+
from lmcache.logging import init_logger
|
|
38
|
+
from lmcache.observability import LMCStatsMonitor, PrometheusLogger
|
|
39
|
+
from lmcache.utils import CacheStoreEvent, _lmcache_nvtx_annotate, cdiv
|
|
40
|
+
from lmcache.v1.cache_engine import LMCacheEngine
|
|
41
|
+
from lmcache.v1.compute.blend import LMCBlenderBuilder
|
|
42
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
43
|
+
from lmcache.v1.config_base import validate_and_set_config_value
|
|
44
|
+
from lmcache.v1.manager import LMCacheManager
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
# Third Party
|
|
48
|
+
from vllm.attention.backends.abstract import AttentionMetadata
|
|
49
|
+
from vllm.forward_context import ForwardContext
|
|
50
|
+
from vllm.multimodal.inputs import PlaceholderRange
|
|
51
|
+
from vllm.v1.core.kv_cache_manager import KVCacheManager
|
|
52
|
+
from vllm.v1.core.sched.output import NewRequestData
|
|
53
|
+
from vllm.v1.request import Request
|
|
54
|
+
|
|
55
|
+
# First Party
|
|
56
|
+
from lmcache.v1.lookup_client.abstract_client import LookupClientInterface
|
|
57
|
+
|
|
58
|
+
logger = init_logger(__name__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class LoadSpec:
|
|
63
|
+
# Number of tokens cached in vLLM
|
|
64
|
+
vllm_cached_tokens: int
|
|
65
|
+
# Number of tokens that are cached in LMCache
|
|
66
|
+
lmcache_cached_tokens: int
|
|
67
|
+
# Whether the scheduler allow us to load the tokens
|
|
68
|
+
can_load: bool
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class SaveSpec:
|
|
73
|
+
# Skip already saved tokens
|
|
74
|
+
skip_leading_tokens: int
|
|
75
|
+
# Whether the scheduler allow us to save the tokens
|
|
76
|
+
can_save: bool
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class DisaggSpec:
|
|
81
|
+
req_id: str
|
|
82
|
+
receiver_id: str
|
|
83
|
+
receiver_host: str
|
|
84
|
+
receiver_init_port: int
|
|
85
|
+
receiver_alloc_port: int
|
|
86
|
+
is_last_prefill: bool = False
|
|
87
|
+
num_transferred_tokens: int = 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
tmp_disagg_tracker: dict[str, DisaggSpec] = {}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def extract_request_configs(sampling_params: SamplingParams) -> Optional[dict]:
|
|
94
|
+
request_configs = None
|
|
95
|
+
if sampling_params and sampling_params.extra_args is not None:
|
|
96
|
+
if kv_transfer_params := sampling_params.extra_args.get("kv_transfer_params"):
|
|
97
|
+
for k, v in kv_transfer_params.items():
|
|
98
|
+
if k.startswith("lmcache."):
|
|
99
|
+
if request_configs is None:
|
|
100
|
+
request_configs = {}
|
|
101
|
+
request_configs[k] = v
|
|
102
|
+
return request_configs
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class RequestTracker:
|
|
107
|
+
# Request id
|
|
108
|
+
req_id: str
|
|
109
|
+
|
|
110
|
+
# Total prompt token length
|
|
111
|
+
prompt_len: int
|
|
112
|
+
|
|
113
|
+
# The token ids that has been scheduled so far
|
|
114
|
+
token_ids: list[int]
|
|
115
|
+
|
|
116
|
+
# The block ids that has been allocated so far
|
|
117
|
+
# NOTE: allocated blocks could be more than the number of tokens
|
|
118
|
+
allocated_block_ids: list[int]
|
|
119
|
+
|
|
120
|
+
# The number of tokens that has been saved
|
|
121
|
+
num_saved_tokens: int = 0
|
|
122
|
+
|
|
123
|
+
# Disagg spec for the request
|
|
124
|
+
disagg_spec: Optional[DisaggSpec] = None
|
|
125
|
+
|
|
126
|
+
# Multimodal hashes and positions
|
|
127
|
+
mm_hashes: Optional[list[str]] = None
|
|
128
|
+
mm_positions: Optional[list["PlaceholderRange"]] = None
|
|
129
|
+
|
|
130
|
+
# The configs of the request, includes tags and other configs
|
|
131
|
+
request_configs: Optional[dict] = None
|
|
132
|
+
|
|
133
|
+
# Whether the request is in decode phase
|
|
134
|
+
is_decode_phase = False
|
|
135
|
+
|
|
136
|
+
# Whether the request cache should be saved
|
|
137
|
+
skip_save: bool = False
|
|
138
|
+
|
|
139
|
+
# The number of tokens that are cached in LMCache for this request
|
|
140
|
+
num_lmcache_cached_tokens: int = 0
|
|
141
|
+
|
|
142
|
+
@_lmcache_nvtx_annotate
|
|
143
|
+
@staticmethod
|
|
144
|
+
def from_new_request(
|
|
145
|
+
lmcache_config: LMCacheEngineConfig,
|
|
146
|
+
new_request: "NewRequestData",
|
|
147
|
+
num_tokens_to_compute: int,
|
|
148
|
+
lmcache_cached_tokens: int,
|
|
149
|
+
skip_save: bool,
|
|
150
|
+
) -> "RequestTracker":
|
|
151
|
+
"""Create the request tracker from a new request.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
lmcache_config (LMCacheEngineConfig): the LMCache engine config.
|
|
155
|
+
new_request (NewRequestData): the new request data.
|
|
156
|
+
num_tokens_to_compute (int): the number of tokens that will
|
|
157
|
+
be 'computed', including the `num_computed_tokens` (vLLM's
|
|
158
|
+
local cache hit) and new tokens that will be scheduled.
|
|
159
|
+
lmcache_cached_tokens (int): the number of tokens that are
|
|
160
|
+
cached in LMCache.
|
|
161
|
+
request_priority (int): the priority of the request
|
|
162
|
+
skip_save (bool): whether the request cache should be saved
|
|
163
|
+
"""
|
|
164
|
+
# vLLM 0.9.0 update: request.block_ids changed from list[int] to
|
|
165
|
+
# tuple[list[int]]
|
|
166
|
+
# Need to check the type of request.block_ids
|
|
167
|
+
|
|
168
|
+
unfolded_block_ids = []
|
|
169
|
+
|
|
170
|
+
if not isinstance(new_request.block_ids[0], list):
|
|
171
|
+
unfolded_block_ids = new_request.block_ids.copy()
|
|
172
|
+
else:
|
|
173
|
+
# According to the vLLM code
|
|
174
|
+
# (https://github.com/vllm-project/vllm/blob/main/vllm/v1/core/
|
|
175
|
+
# sched/scheduler.py#L943),
|
|
176
|
+
# only one KVCacheGroup is supported in connector for now.
|
|
177
|
+
|
|
178
|
+
# TODO: Please support multiple KVCacheGroup in connector.
|
|
179
|
+
# NOTE: Also, `update` method in RequestTracker should be
|
|
180
|
+
# updated accordingly.
|
|
181
|
+
unfolded_block_ids = new_request.block_ids[0].copy()
|
|
182
|
+
|
|
183
|
+
# NOTE: Initialized in `update_state_after_alloc`
|
|
184
|
+
disagg_spec = tmp_disagg_tracker.pop(new_request.req_id, None)
|
|
185
|
+
|
|
186
|
+
request_configs = extract_request_configs(new_request.sampling_params)
|
|
187
|
+
|
|
188
|
+
mm_hashes, mm_positions = extract_mm_features(new_request, modify=True)
|
|
189
|
+
|
|
190
|
+
return RequestTracker(
|
|
191
|
+
req_id=new_request.req_id,
|
|
192
|
+
prompt_len=len(new_request.prompt_token_ids),
|
|
193
|
+
token_ids=new_request.prompt_token_ids[:num_tokens_to_compute].copy(),
|
|
194
|
+
allocated_block_ids=unfolded_block_ids,
|
|
195
|
+
num_saved_tokens=lmcache_cached_tokens,
|
|
196
|
+
disagg_spec=disagg_spec,
|
|
197
|
+
mm_hashes=mm_hashes,
|
|
198
|
+
mm_positions=mm_positions,
|
|
199
|
+
skip_save=skip_save,
|
|
200
|
+
request_configs=request_configs,
|
|
201
|
+
num_lmcache_cached_tokens=lmcache_cached_tokens,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def update(
|
|
205
|
+
self,
|
|
206
|
+
new_token_ids: list[int],
|
|
207
|
+
new_block_ids: Union[Optional[tuple[list[int], ...]], list[int]],
|
|
208
|
+
preempted: bool = False,
|
|
209
|
+
lmcache_cached_tokens: int = 0,
|
|
210
|
+
vllm_cached_tokens: int = 0,
|
|
211
|
+
all_token_ids: Optional[list[int]] = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Update the request tracker when a running request is
|
|
214
|
+
scheduled again
|
|
215
|
+
|
|
216
|
+
vllm_cached_tokens: the number of tokens that are cached in vLLM
|
|
217
|
+
is only used for preempted requests
|
|
218
|
+
all_token_ids: the full token list from the vLLM request, used to
|
|
219
|
+
restore token_ids for preempted requests to ensure chunk keys match
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
if new_block_ids is None:
|
|
223
|
+
# https://github.com/vllm-project/vllm/commit/
|
|
224
|
+
# b029de9902aa3ac58806c8c17776c7074175b6db#
|
|
225
|
+
# diff-cafd89ce8a698a56acb24ada62831cbc7a980782f78a52d1742ba238031f296cL94
|
|
226
|
+
new_block_ids = []
|
|
227
|
+
elif len(new_block_ids) == 0:
|
|
228
|
+
new_block_ids = []
|
|
229
|
+
elif isinstance(new_block_ids, tuple):
|
|
230
|
+
new_block_ids = new_block_ids[0]
|
|
231
|
+
elif isinstance(new_block_ids, list):
|
|
232
|
+
# If input is a list, flatten it to handle potential nesting.
|
|
233
|
+
# This also correctly processes already-flat lists.
|
|
234
|
+
new_block_ids = [
|
|
235
|
+
i
|
|
236
|
+
for elem in new_block_ids
|
|
237
|
+
for i in (elem if isinstance(elem, list) else [elem])
|
|
238
|
+
]
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError(f"Unsupported new_block_ids type {type(new_block_ids)}")
|
|
241
|
+
|
|
242
|
+
if preempted:
|
|
243
|
+
assert all_token_ids is not None, (
|
|
244
|
+
f"Preempted request {self.req_id} has no all_token_ids"
|
|
245
|
+
)
|
|
246
|
+
# the block ids will change after preemption
|
|
247
|
+
self.allocated_block_ids = new_block_ids
|
|
248
|
+
# reset the number of saved tokens
|
|
249
|
+
self.num_saved_tokens = lmcache_cached_tokens
|
|
250
|
+
num_computed_tokens = max(lmcache_cached_tokens, vllm_cached_tokens)
|
|
251
|
+
|
|
252
|
+
# FIX: For preempted requests, restore token_ids from the full
|
|
253
|
+
# token list to ensure chunk keys match what was used during
|
|
254
|
+
# lookup. The lookup uses request.all_token_ids, so we need the
|
|
255
|
+
# same tokens for retrieve.
|
|
256
|
+
num_tokens_needed = max(
|
|
257
|
+
num_computed_tokens + len(new_token_ids),
|
|
258
|
+
lmcache_cached_tokens,
|
|
259
|
+
)
|
|
260
|
+
self.token_ids = all_token_ids[:num_tokens_needed]
|
|
261
|
+
else:
|
|
262
|
+
self.allocated_block_ids.extend(new_block_ids)
|
|
263
|
+
self.token_ids.extend(new_token_ids)
|
|
264
|
+
|
|
265
|
+
# When a request is scheduled again, and the number of new tokens
|
|
266
|
+
# is 1 (excluding chunked prefill), the request is in decode phase.
|
|
267
|
+
# TODO: Need to further exclude the case of chunked prefill with 1 token.
|
|
268
|
+
if len(new_token_ids) == 1:
|
|
269
|
+
self.is_decode_phase = True
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class ReqMeta:
|
|
274
|
+
# Request id
|
|
275
|
+
req_id: str
|
|
276
|
+
# Request tokens
|
|
277
|
+
token_ids: list[int] # torch.Tensor
|
|
278
|
+
# Slot mapping
|
|
279
|
+
slot_mapping: torch.Tensor
|
|
280
|
+
|
|
281
|
+
# Whether is last prefill or not
|
|
282
|
+
is_last_prefill: bool = False
|
|
283
|
+
|
|
284
|
+
# Skip save or not
|
|
285
|
+
save_spec: Optional[SaveSpec] = None
|
|
286
|
+
# load_spec
|
|
287
|
+
load_spec: Optional[LoadSpec] = None
|
|
288
|
+
# disagg spec
|
|
289
|
+
disagg_spec: Optional[DisaggSpec] = None
|
|
290
|
+
# the configs of the request
|
|
291
|
+
request_configs: Optional[dict] = None
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def from_request_tracker(
|
|
295
|
+
tracker: RequestTracker,
|
|
296
|
+
block_size: int,
|
|
297
|
+
lmcache_chunk_size: int = 256,
|
|
298
|
+
load_spec: Optional[LoadSpec] = None,
|
|
299
|
+
discard_partial_chunks: bool = True,
|
|
300
|
+
save_decode_cache: bool = False,
|
|
301
|
+
) -> Optional["ReqMeta"]:
|
|
302
|
+
"""Create the request metadata from a request tracker.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
tracker (RequestTracker): the request tracker.
|
|
306
|
+
block_size (int): the block size in vLLM.
|
|
307
|
+
lmcache_chunk_size (int): the chunk size for LMCache.
|
|
308
|
+
load_spec (Optional[LoadSpec]): the load spec for KV cache loading.
|
|
309
|
+
discard_partial_chunks (bool): whether to discard partial chunks.
|
|
310
|
+
save_decode_cache (bool): whether to save the cache in decode phase.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
the request metadata if we need to perform load/save
|
|
314
|
+
operations, None otherwise.
|
|
315
|
+
"""
|
|
316
|
+
input_token_ids = tracker.token_ids
|
|
317
|
+
input_token_len = len(input_token_ids)
|
|
318
|
+
|
|
319
|
+
is_last_prefill = False
|
|
320
|
+
if input_token_len >= tracker.prompt_len:
|
|
321
|
+
is_last_prefill = True
|
|
322
|
+
|
|
323
|
+
# For save operation: do not save if the following condition is met
|
|
324
|
+
# 1. has already been saved before (num_saved_tokens > 0)
|
|
325
|
+
# 2. number of unsaved tokens is not reached the chunk boundary
|
|
326
|
+
# 3. if save_decode_cache is False and it is in decode phase
|
|
327
|
+
|
|
328
|
+
skip_leading_tokens = tracker.num_saved_tokens
|
|
329
|
+
chunk_boundary = (
|
|
330
|
+
cdiv(tracker.num_saved_tokens + 1, lmcache_chunk_size) * lmcache_chunk_size
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# NOTE(vladnosiv): for disagg, you cannot skip saving, as saving is a transfer
|
|
334
|
+
# Check if request_configs has lmcache.skip_save set to True
|
|
335
|
+
request_skip = (tracker.request_configs or {}).get("lmcache.skip_save", False)
|
|
336
|
+
|
|
337
|
+
skip_save = tracker.disagg_spec is None and (
|
|
338
|
+
tracker.skip_save
|
|
339
|
+
or (tracker.num_saved_tokens > 0 and input_token_len < chunk_boundary)
|
|
340
|
+
or (tracker.is_decode_phase and not save_decode_cache)
|
|
341
|
+
or request_skip
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if skip_save and load_spec is None:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
# Calculate number of tokens to save based on discard_partial_chunks
|
|
348
|
+
# setting
|
|
349
|
+
|
|
350
|
+
# NOTE(vladnosiv): for the input_token_len chunk prefill,
|
|
351
|
+
# we are required to discard partial chunks,
|
|
352
|
+
# as new tokens will be added in the next iteration.
|
|
353
|
+
if not is_last_prefill or discard_partial_chunks:
|
|
354
|
+
num_tokens_to_save = (
|
|
355
|
+
input_token_len // lmcache_chunk_size * lmcache_chunk_size
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
num_tokens_to_save = input_token_len
|
|
359
|
+
|
|
360
|
+
# If we need to save, update the number of saved tokens
|
|
361
|
+
if not skip_save:
|
|
362
|
+
tracker.num_saved_tokens = num_tokens_to_save
|
|
363
|
+
save_spec = SaveSpec(skip_leading_tokens, not skip_save)
|
|
364
|
+
|
|
365
|
+
# Calculate the token ids and slot mappings for load and save
|
|
366
|
+
token_ids = input_token_ids[:num_tokens_to_save]
|
|
367
|
+
|
|
368
|
+
# If the request has multimodal hashes, apply them to the token ids
|
|
369
|
+
if tracker.mm_hashes:
|
|
370
|
+
# TODO: Optimize this
|
|
371
|
+
token_ids = torch.tensor(token_ids)
|
|
372
|
+
assert tracker.mm_positions is not None, (
|
|
373
|
+
"tracker got mm_hashes but no mm_positions"
|
|
374
|
+
)
|
|
375
|
+
apply_mm_hashes_to_token_ids(
|
|
376
|
+
token_ids, tracker.mm_hashes, tracker.mm_positions
|
|
377
|
+
)
|
|
378
|
+
token_ids = token_ids.tolist()
|
|
379
|
+
|
|
380
|
+
num_blocks = len(tracker.allocated_block_ids)
|
|
381
|
+
|
|
382
|
+
if len(token_ids) > num_blocks * block_size:
|
|
383
|
+
logger.error(
|
|
384
|
+
"The number of tokens is more than the number of blocks"
|
|
385
|
+
" for request %s. "
|
|
386
|
+
"Something might be wrong in scheduling logic!",
|
|
387
|
+
tracker.req_id,
|
|
388
|
+
)
|
|
389
|
+
logger.error(
|
|
390
|
+
"Num tokens: %d, num blocks: %d, block size: %d",
|
|
391
|
+
len(token_ids),
|
|
392
|
+
num_blocks,
|
|
393
|
+
block_size,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
block_ids = torch.tensor(tracker.allocated_block_ids, dtype=torch.long)
|
|
397
|
+
block_offsets = torch.arange(0, block_size, dtype=torch.long)
|
|
398
|
+
slot_mapping = (
|
|
399
|
+
block_offsets.reshape((1, block_size))
|
|
400
|
+
+ block_ids.reshape((num_blocks, 1)) * block_size
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
slot_mapping = slot_mapping.flatten()[: len(token_ids)]
|
|
404
|
+
assert slot_mapping.dtype == torch.long # TODO: this could be removed
|
|
405
|
+
|
|
406
|
+
# For load operation: log if the request is scheduled to load
|
|
407
|
+
if load_spec is not None and load_spec.can_load:
|
|
408
|
+
logger.debug(
|
|
409
|
+
"Scheduled to load %d tokens (%d cached in vLLM) for request %s",
|
|
410
|
+
load_spec.lmcache_cached_tokens,
|
|
411
|
+
load_spec.vllm_cached_tokens,
|
|
412
|
+
tracker.req_id,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Note: We keep load_spec even when can_load=False to pass metrics to worker
|
|
416
|
+
return ReqMeta(
|
|
417
|
+
req_id=tracker.req_id,
|
|
418
|
+
token_ids=token_ids,
|
|
419
|
+
slot_mapping=slot_mapping,
|
|
420
|
+
is_last_prefill=is_last_prefill,
|
|
421
|
+
save_spec=save_spec,
|
|
422
|
+
load_spec=load_spec,
|
|
423
|
+
disagg_spec=tracker.disagg_spec,
|
|
424
|
+
request_configs=tracker.request_configs,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@dataclass
|
|
429
|
+
class LMCacheConnectorMetadata(KVConnectorMetadata):
|
|
430
|
+
requests: list[ReqMeta] = field(default_factory=list)
|
|
431
|
+
|
|
432
|
+
@_lmcache_nvtx_annotate
|
|
433
|
+
def add_request(self, req_meta: ReqMeta) -> None:
|
|
434
|
+
"""Add a request to the metadata.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
req_meta (ReqMeta): the request metadata.
|
|
438
|
+
"""
|
|
439
|
+
self.requests.append(req_meta)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class LMCacheConnectorV1Impl:
|
|
443
|
+
def __init__(
|
|
444
|
+
self,
|
|
445
|
+
vllm_config: "VllmConfig",
|
|
446
|
+
role: KVConnectorRole,
|
|
447
|
+
parent: KVConnectorBase_V1,
|
|
448
|
+
):
|
|
449
|
+
self._parent = parent
|
|
450
|
+
self._vllm_config = vllm_config
|
|
451
|
+
self._role = role
|
|
452
|
+
self.device = vllm_config.device_config.device
|
|
453
|
+
self.kv_role = vllm_config.kv_transfer_config.kv_role
|
|
454
|
+
self.worker_count = vllm_config.parallel_config.tensor_parallel_size
|
|
455
|
+
|
|
456
|
+
# Load and configure LMCache config
|
|
457
|
+
config = lmcache_get_or_create_config()
|
|
458
|
+
assert isinstance(config, LMCacheEngineConfig), (
|
|
459
|
+
"LMCache v1 configuration is should be passed for vLLM v1."
|
|
460
|
+
)
|
|
461
|
+
self._apply_extra_config(config, vllm_config)
|
|
462
|
+
self.config = config
|
|
463
|
+
|
|
464
|
+
service_factory = VllmServiceFactory(config, vllm_config, role.name.lower())
|
|
465
|
+
self._manager = LMCacheManager(config, service_factory, connector=self)
|
|
466
|
+
|
|
467
|
+
# Start services managed by LMCacheManager
|
|
468
|
+
self._manager.start_services()
|
|
469
|
+
|
|
470
|
+
# Initialize connector-specific state
|
|
471
|
+
self._init_connector_state(role, vllm_config, config)
|
|
472
|
+
|
|
473
|
+
# Setup metrics for monitoring data structures
|
|
474
|
+
self._setup_metrics()
|
|
475
|
+
|
|
476
|
+
logger.info(
|
|
477
|
+
"LMCache initialized for role %s with version %s, "
|
|
478
|
+
"vllm version %s, lmcache cache_engine metadata: %s",
|
|
479
|
+
role,
|
|
480
|
+
utils.get_version(),
|
|
481
|
+
VLLM_VERSION,
|
|
482
|
+
getattr(self.lmcache_engine, "metadata", None),
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def _apply_extra_config(
|
|
486
|
+
self, config: LMCacheEngineConfig, vllm_config: "VllmConfig"
|
|
487
|
+
) -> None:
|
|
488
|
+
"""Apply extra config from vLLM to LMCache config."""
|
|
489
|
+
kv_connector_extra_config = (
|
|
490
|
+
vllm_config.kv_transfer_config.kv_connector_extra_config
|
|
491
|
+
)
|
|
492
|
+
if kv_connector_extra_config:
|
|
493
|
+
for key, value in kv_connector_extra_config.items():
|
|
494
|
+
if key.startswith("lmcache."):
|
|
495
|
+
config_key = key[8:] # Remove "lmcache." prefix
|
|
496
|
+
if validate_and_set_config_value(config, config_key, value):
|
|
497
|
+
logger.info(
|
|
498
|
+
"Updated config %s from vLLM extra config",
|
|
499
|
+
config_key,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
def _init_connector_state(
|
|
503
|
+
self,
|
|
504
|
+
role: KVConnectorRole,
|
|
505
|
+
vllm_config: "VllmConfig",
|
|
506
|
+
config: LMCacheEngineConfig,
|
|
507
|
+
) -> None:
|
|
508
|
+
"""Initialize connector-specific state variables."""
|
|
509
|
+
self.async_loading = config.enable_async_loading
|
|
510
|
+
self.layerwise_retrievers: list[
|
|
511
|
+
Generator[Optional[torch.Tensor], None, None]
|
|
512
|
+
] = []
|
|
513
|
+
self._layerwise_save_storers: dict[
|
|
514
|
+
str, Generator[Optional[torch.Tensor], None, None]
|
|
515
|
+
] = {}
|
|
516
|
+
self._stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
517
|
+
|
|
518
|
+
# Role-specific initialization
|
|
519
|
+
if role == KVConnectorRole.SCHEDULER:
|
|
520
|
+
self._unfinished_requests: dict[str, "Request"] = {}
|
|
521
|
+
else:
|
|
522
|
+
self.use_layerwise = config.use_layerwise
|
|
523
|
+
self.enable_blending = config.enable_blending
|
|
524
|
+
|
|
525
|
+
if self.enable_blending:
|
|
526
|
+
assert self.lmcache_engine is not None
|
|
527
|
+
assert self.lmcache_engine.gpu_connector is not None, (
|
|
528
|
+
"GPU connector must be available for blending"
|
|
529
|
+
)
|
|
530
|
+
self.blender = LMCBlenderBuilder.get_or_create(
|
|
531
|
+
ENGINE_NAME,
|
|
532
|
+
self.lmcache_engine,
|
|
533
|
+
self.lmcache_engine.gpu_connector,
|
|
534
|
+
config,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# Legacy compatibility check
|
|
538
|
+
self._check_legacy_register_kv_caches()
|
|
539
|
+
|
|
540
|
+
self.kv_caches: dict[str, torch.Tensor] = {}
|
|
541
|
+
self._block_size = vllm_config.cache_config.block_size
|
|
542
|
+
self.load_specs: dict[str, LoadSpec] = {}
|
|
543
|
+
self.kv_cache_manager: Optional["KVCacheManager"] = None
|
|
544
|
+
self._request_trackers: dict[str, RequestTracker] = {}
|
|
545
|
+
|
|
546
|
+
self._discard_partial_chunks = (
|
|
547
|
+
vllm_config.kv_transfer_config.get_from_extra_config(
|
|
548
|
+
"discard_partial_chunks", False
|
|
549
|
+
)
|
|
550
|
+
or not config.save_unfull_chunk
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
self._lmcache_chunk_size = config.chunk_size
|
|
554
|
+
|
|
555
|
+
self.skip_last_n_tokens = vllm_config.kv_transfer_config.get_from_extra_config(
|
|
556
|
+
"skip_last_n_tokens", 0
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
self.num_layers = vllm_config.model_config.get_num_layers(
|
|
560
|
+
vllm_config.parallel_config
|
|
561
|
+
)
|
|
562
|
+
self.current_layer = 0
|
|
563
|
+
|
|
564
|
+
self.force_skip_save = bool(os.environ.get("LMCACHE_FORCE_SKIP_SAVE", False))
|
|
565
|
+
self._requests_priority: dict[str, int] = {}
|
|
566
|
+
self._invalid_block_ids: set[int] = set()
|
|
567
|
+
|
|
568
|
+
def _check_legacy_register_kv_caches(self) -> None:
|
|
569
|
+
"""Check for legacy connector without register_kv_caches implementation."""
|
|
570
|
+
if self.lmcache_engine is None:
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
child_class = self._parent.__class__
|
|
574
|
+
parent_class = KVConnectorBase_V1
|
|
575
|
+
child_method = getattr(child_class, "register_kv_caches", None)
|
|
576
|
+
parent_method = getattr(parent_class, "register_kv_caches", None)
|
|
577
|
+
|
|
578
|
+
if child_method is None or parent_method is None:
|
|
579
|
+
implements = False
|
|
580
|
+
else:
|
|
581
|
+
implements = child_method is not parent_method
|
|
582
|
+
|
|
583
|
+
if not implements:
|
|
584
|
+
logger.warning(
|
|
585
|
+
"Please use the latest lmcache connector, otherwise some "
|
|
586
|
+
"features may not work, such as DSA"
|
|
587
|
+
)
|
|
588
|
+
self._manager.post_init()
|
|
589
|
+
|
|
590
|
+
# ==================== Property Accessors ====================
|
|
591
|
+
|
|
592
|
+
@property
|
|
593
|
+
def lmcache_engine(self) -> Optional[LMCacheEngine]:
|
|
594
|
+
"""Get the LMCache engine instance from manager."""
|
|
595
|
+
return self._manager.lmcache_engine
|
|
596
|
+
|
|
597
|
+
@property
|
|
598
|
+
def lmcache_engine_metadata(self):
|
|
599
|
+
"""Get the LMCache engine metadata from manager."""
|
|
600
|
+
return self._manager.lmcache_engine_metadata
|
|
601
|
+
|
|
602
|
+
@property
|
|
603
|
+
def lookup_client(self) -> Optional["LookupClientInterface"]:
|
|
604
|
+
"""Get the lookup client from manager."""
|
|
605
|
+
return self._manager.lookup_client
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def lookup_server(self):
|
|
609
|
+
"""Get the lookup server from manager."""
|
|
610
|
+
return self._manager.lookup_server
|
|
611
|
+
|
|
612
|
+
def _setup_metrics(self):
|
|
613
|
+
"""Setup metrics for monitoring data structures in the connector."""
|
|
614
|
+
prometheus_logger = PrometheusLogger.GetInstanceOrNone()
|
|
615
|
+
if prometheus_logger is None:
|
|
616
|
+
logger.warning(
|
|
617
|
+
"PrometheusLogger is not initialized, "
|
|
618
|
+
"connector metrics will not be collected"
|
|
619
|
+
)
|
|
620
|
+
return
|
|
621
|
+
|
|
622
|
+
# Set up metrics for scheduler-specific and general data structures
|
|
623
|
+
metrics_map = {
|
|
624
|
+
"_unfinished_requests": "scheduler_unfinished_requests_count",
|
|
625
|
+
"load_specs": "connector_load_specs_count",
|
|
626
|
+
"_request_trackers": "connector_request_trackers_count",
|
|
627
|
+
"kv_caches": "connector_kv_caches_count",
|
|
628
|
+
"layerwise_retrievers": "connector_layerwise_retrievers_count",
|
|
629
|
+
"_invalid_block_ids": "connector_invalid_block_ids_count",
|
|
630
|
+
"_requests_priority": "connector_requests_priority_count",
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
for attr_name, metric_name in metrics_map.items():
|
|
634
|
+
if hasattr(self, attr_name):
|
|
635
|
+
metric = getattr(prometheus_logger, metric_name)
|
|
636
|
+
# Use a default argument in the lambda to capture
|
|
637
|
+
# the current value of `attr_name`
|
|
638
|
+
# to avoid issues with late binding in closures.
|
|
639
|
+
metric.set_function(lambda name=attr_name: len(getattr(self, name)))
|
|
640
|
+
|
|
641
|
+
def get_inference_info(self) -> dict:
|
|
642
|
+
"""Get inference information including vLLM config and related details.
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
dict: Dictionary containing inference information
|
|
646
|
+
"""
|
|
647
|
+
# Get vLLM config information
|
|
648
|
+
vllm_config = self._vllm_config
|
|
649
|
+
|
|
650
|
+
# Use vLLM config's string representation and add specific configs
|
|
651
|
+
inference_info = {
|
|
652
|
+
"vllm_version": VLLM_VERSION,
|
|
653
|
+
"lmcache_version": utils.get_version(),
|
|
654
|
+
"vllm_config": str(vllm_config),
|
|
655
|
+
"model_config": {
|
|
656
|
+
"model": getattr(vllm_config.model_config, "model", None),
|
|
657
|
+
"dtype": str(getattr(vllm_config.model_config, "dtype", None)),
|
|
658
|
+
"max_model_len": getattr(
|
|
659
|
+
vllm_config.model_config, "max_model_len", None
|
|
660
|
+
),
|
|
661
|
+
"vocab_size": getattr(vllm_config.model_config, "vocab_size", None),
|
|
662
|
+
"num_layers": getattr(
|
|
663
|
+
vllm_config.model_config, "get_num_layers", lambda _: None
|
|
664
|
+
)(vllm_config.parallel_config),
|
|
665
|
+
"num_attention_heads": getattr(
|
|
666
|
+
vllm_config.model_config, "get_num_attention_heads", lambda _: None
|
|
667
|
+
)(vllm_config.parallel_config),
|
|
668
|
+
"num_kv_heads": getattr(
|
|
669
|
+
vllm_config.model_config, "get_num_kv_heads", lambda _: None
|
|
670
|
+
)(vllm_config.parallel_config),
|
|
671
|
+
"head_size": getattr(
|
|
672
|
+
vllm_config.model_config, "get_head_size", lambda: None
|
|
673
|
+
)(),
|
|
674
|
+
},
|
|
675
|
+
"cache_config": {
|
|
676
|
+
"block_size": getattr(vllm_config.cache_config, "block_size", None),
|
|
677
|
+
"cache_dtype": str(
|
|
678
|
+
getattr(vllm_config.cache_config, "cache_dtype", None)
|
|
679
|
+
),
|
|
680
|
+
"gpu_memory_utilization": getattr(
|
|
681
|
+
vllm_config.cache_config, "gpu_memory_utilization", None
|
|
682
|
+
),
|
|
683
|
+
"swap_space": getattr(vllm_config.cache_config, "swap_space", None),
|
|
684
|
+
"enable_prefix_caching": getattr(
|
|
685
|
+
vllm_config.cache_config, "enable_prefix_caching", None
|
|
686
|
+
),
|
|
687
|
+
},
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return inference_info
|
|
691
|
+
|
|
692
|
+
def get_inference_version(self) -> str:
|
|
693
|
+
"""Get vLLM version information.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
str: vLLM version string
|
|
697
|
+
"""
|
|
698
|
+
return VLLM_VERSION
|
|
699
|
+
|
|
700
|
+
def _build_kv_layer_groups(self):
|
|
701
|
+
# Build KV layer groups structure if not already built
|
|
702
|
+
if self.lmcache_engine is not None:
|
|
703
|
+
assert len(self.kv_caches) > 0
|
|
704
|
+
kv_layer_groups_manager = (
|
|
705
|
+
self.lmcache_engine.metadata.kv_layer_groups_manager
|
|
706
|
+
)
|
|
707
|
+
kv_layer_groups_manager.build_kv_layer_groups(self.kv_caches)
|
|
708
|
+
|
|
709
|
+
# TODO(chunxiaozheng): in the latest lmcache_connector, we use `register_kv_caches`
|
|
710
|
+
# to init self.kv_caches, we keep it in order to be compatible with old versions
|
|
711
|
+
# and will be removed in the future.
|
|
712
|
+
@_lmcache_nvtx_annotate
|
|
713
|
+
def _init_kv_caches_from_forward_context(self, forward_context: "ForwardContext"):
|
|
714
|
+
for layer_name in forward_context.no_compile_layers:
|
|
715
|
+
attn_layer = forward_context.no_compile_layers[layer_name]
|
|
716
|
+
if not hasattr(attn_layer, "kv_cache"):
|
|
717
|
+
logger.debug("The layer %s does not have kv_cache, skip it", layer_name)
|
|
718
|
+
continue
|
|
719
|
+
|
|
720
|
+
if layer_name not in self.kv_caches:
|
|
721
|
+
self.kv_caches[layer_name] = attn_layer.kv_cache[
|
|
722
|
+
forward_context.virtual_engine
|
|
723
|
+
]
|
|
724
|
+
|
|
725
|
+
self._build_kv_layer_groups()
|
|
726
|
+
|
|
727
|
+
####################
|
|
728
|
+
# Worker side APIs
|
|
729
|
+
####################
|
|
730
|
+
@_lmcache_nvtx_annotate
|
|
731
|
+
def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
|
|
732
|
+
logger.info("Registering KV caches")
|
|
733
|
+
# TODO(chunxiaozheng): `_init_kv_caches_from_forward_context` is
|
|
734
|
+
# not called, we should consider removing it.
|
|
735
|
+
assert len(self.kv_caches) == 0 and len(kv_caches) > 0
|
|
736
|
+
self.kv_caches = kv_caches
|
|
737
|
+
self._build_kv_layer_groups()
|
|
738
|
+
self._manager.post_init()
|
|
739
|
+
|
|
740
|
+
@_lmcache_nvtx_annotate
|
|
741
|
+
def start_load_kv(self, forward_context: "ForwardContext", **kwargs) -> None:
|
|
742
|
+
"""Start loading the KV cache from the connector buffer to vLLM's
|
|
743
|
+
paged KV buffer.
|
|
744
|
+
|
|
745
|
+
Args:
|
|
746
|
+
forward_context (ForwardContext): the forward context.
|
|
747
|
+
**kwargs: additional arguments for the load operation
|
|
748
|
+
|
|
749
|
+
Note:
|
|
750
|
+
The number of elements in kv_caches and layer_names should be
|
|
751
|
+
the same.
|
|
752
|
+
"""
|
|
753
|
+
self.current_layer = 0
|
|
754
|
+
|
|
755
|
+
if len(self.kv_caches) == 0:
|
|
756
|
+
logger.warning(
|
|
757
|
+
"Please update LMCacheConnector, "
|
|
758
|
+
"use register_kv_caches to init kv_caches"
|
|
759
|
+
)
|
|
760
|
+
self._init_kv_caches_from_forward_context(forward_context)
|
|
761
|
+
|
|
762
|
+
metadata = self._parent._get_connector_metadata()
|
|
763
|
+
assert isinstance(metadata, LMCacheConnectorMetadata)
|
|
764
|
+
|
|
765
|
+
assert len(self.kv_caches) > 0
|
|
766
|
+
kvcaches = list(self.kv_caches.values())
|
|
767
|
+
|
|
768
|
+
attn_metadata = forward_context.attn_metadata
|
|
769
|
+
if attn_metadata is None:
|
|
770
|
+
logger.debug("In connector.start_load_kv, but the attn_metadata is None")
|
|
771
|
+
return
|
|
772
|
+
|
|
773
|
+
assert self.lmcache_engine is not None
|
|
774
|
+
|
|
775
|
+
self.layerwise_retrievers = []
|
|
776
|
+
|
|
777
|
+
for idx, request in enumerate(metadata.requests):
|
|
778
|
+
if request.load_spec is None or not request.load_spec.can_load:
|
|
779
|
+
continue
|
|
780
|
+
last_idx = idx
|
|
781
|
+
|
|
782
|
+
for idx, request in enumerate(metadata.requests):
|
|
783
|
+
# Update metrics for all requests that have a load_spec
|
|
784
|
+
if request.load_spec is not None:
|
|
785
|
+
self._stats_monitor.update_interval_vllm_hit_tokens(
|
|
786
|
+
request.load_spec.vllm_cached_tokens
|
|
787
|
+
)
|
|
788
|
+
self._stats_monitor.update_interval_prompt_tokens(
|
|
789
|
+
len(request.token_ids)
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
if request.load_spec is None or not request.load_spec.can_load:
|
|
793
|
+
continue
|
|
794
|
+
|
|
795
|
+
tokens = request.token_ids
|
|
796
|
+
# TODO: have a pre-allocated buffer to hold the slot_mappings
|
|
797
|
+
slot_mapping = request.slot_mapping.to(self.device)
|
|
798
|
+
assert len(tokens) == len(slot_mapping)
|
|
799
|
+
|
|
800
|
+
token_mask = torch.ones(len(tokens), dtype=torch.bool)
|
|
801
|
+
masked_token_count = (
|
|
802
|
+
request.load_spec.vllm_cached_tokens
|
|
803
|
+
// self._lmcache_chunk_size
|
|
804
|
+
* self._lmcache_chunk_size
|
|
805
|
+
)
|
|
806
|
+
token_mask[:masked_token_count] = False
|
|
807
|
+
|
|
808
|
+
lmcache_cached_tokens = request.load_spec.lmcache_cached_tokens
|
|
809
|
+
if self.use_layerwise:
|
|
810
|
+
if idx == last_idx:
|
|
811
|
+
sync = True
|
|
812
|
+
else:
|
|
813
|
+
sync = False
|
|
814
|
+
# NOTE(Jiayi): Perform blending before layerwise prefix caching
|
|
815
|
+
if self.enable_blending:
|
|
816
|
+
# TODO(Jiayi): Need to make prefix caching and blending compatible
|
|
817
|
+
self.blender.blend(
|
|
818
|
+
tokens[:lmcache_cached_tokens],
|
|
819
|
+
token_mask[:lmcache_cached_tokens],
|
|
820
|
+
kvcaches=kvcaches,
|
|
821
|
+
slot_mapping=slot_mapping[:lmcache_cached_tokens],
|
|
822
|
+
vllm_cached_tokens=request.load_spec.vllm_cached_tokens,
|
|
823
|
+
)
|
|
824
|
+
else:
|
|
825
|
+
layerwise_retriever = self.lmcache_engine.retrieve_layer(
|
|
826
|
+
tokens[:lmcache_cached_tokens],
|
|
827
|
+
token_mask[:lmcache_cached_tokens],
|
|
828
|
+
kvcaches=kvcaches,
|
|
829
|
+
slot_mapping=slot_mapping[:lmcache_cached_tokens],
|
|
830
|
+
vllm_cached_tokens=request.load_spec.vllm_cached_tokens,
|
|
831
|
+
sync=sync,
|
|
832
|
+
)
|
|
833
|
+
# NOTE: retrieve for two layers at the first layer
|
|
834
|
+
next(layerwise_retriever)
|
|
835
|
+
next(layerwise_retriever)
|
|
836
|
+
self.layerwise_retrievers.append(layerwise_retriever)
|
|
837
|
+
else:
|
|
838
|
+
ret_token_mask = self.lmcache_engine.retrieve(
|
|
839
|
+
tokens[:lmcache_cached_tokens],
|
|
840
|
+
token_mask[:lmcache_cached_tokens],
|
|
841
|
+
kvcaches=kvcaches,
|
|
842
|
+
slot_mapping=slot_mapping[:lmcache_cached_tokens],
|
|
843
|
+
vllm_cached_tokens=request.load_spec.vllm_cached_tokens,
|
|
844
|
+
request_configs=request.request_configs,
|
|
845
|
+
req_id=request.req_id,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
# Check the result
|
|
849
|
+
num_retrieved_tokens = ret_token_mask.sum().item()
|
|
850
|
+
num_expected_tokens = (
|
|
851
|
+
lmcache_cached_tokens - request.load_spec.vllm_cached_tokens
|
|
852
|
+
)
|
|
853
|
+
if num_retrieved_tokens < num_expected_tokens:
|
|
854
|
+
logger.error(
|
|
855
|
+
"Request %s"
|
|
856
|
+
"The number of retrieved tokens is less than the "
|
|
857
|
+
"expected number of tokens! This should not happen!",
|
|
858
|
+
request.req_id,
|
|
859
|
+
)
|
|
860
|
+
logger.error(
|
|
861
|
+
"Num retrieved tokens: %d, num expected tokens: %d",
|
|
862
|
+
num_retrieved_tokens,
|
|
863
|
+
num_expected_tokens,
|
|
864
|
+
)
|
|
865
|
+
"""
|
|
866
|
+
Report failed block IDs in case of partial failure.
|
|
867
|
+
"""
|
|
868
|
+
missing_blocks = self.record_failed_blocks(
|
|
869
|
+
request.req_id,
|
|
870
|
+
token_mask[:lmcache_cached_tokens],
|
|
871
|
+
ret_token_mask,
|
|
872
|
+
slot_mapping[:lmcache_cached_tokens],
|
|
873
|
+
)
|
|
874
|
+
self._invalid_block_ids.update(missing_blocks)
|
|
875
|
+
|
|
876
|
+
def record_failed_blocks(
|
|
877
|
+
self,
|
|
878
|
+
request_id: str,
|
|
879
|
+
expected_mask: torch.Tensor,
|
|
880
|
+
ret_mask: torch.Tensor,
|
|
881
|
+
slot_mapping: torch.Tensor,
|
|
882
|
+
) -> set[int]:
|
|
883
|
+
"""Record block IDs associated with failed load attempts.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
request_id: request id from vLLM.
|
|
887
|
+
expected_mask: Boolean tensor indicating which tokens were expected to
|
|
888
|
+
be loaded from LMCache. True means the token should be loaded,
|
|
889
|
+
False means the token is already cached in vLLM and does not need
|
|
890
|
+
to be loaded from LMCache.
|
|
891
|
+
ret_mask: Boolean tensor indicating which tokens were actually
|
|
892
|
+
successfully retrieved from LMCache. True means the token was
|
|
893
|
+
successfully loaded. For example, if 256 tokens are expected to be
|
|
894
|
+
loaded, but only 192 tokens are successfully loaded, then the
|
|
895
|
+
ret_mask will be a tensor of 256 items like [T, T, ..., F, F, ...]
|
|
896
|
+
where the first 192 elements are True and the last 64 elements
|
|
897
|
+
are False.
|
|
898
|
+
slot_mapping: Tensor indicating slot IDs for each token. The block
|
|
899
|
+
ID is computed by dividing the slot ID by the block size.
|
|
900
|
+
|
|
901
|
+
Example:
|
|
902
|
+
expected_mask = [F, T, T, T] meaning the 1st is in vLLM cache
|
|
903
|
+
ret_mask = [F, T, F, F] meaning failure from loading the 3rd
|
|
904
|
+
missing_mask = expected_mask & ~ret_mask = [F, F, T, T]
|
|
905
|
+
missing_indices = [2, 3]
|
|
906
|
+
then missing_blocks is calculated from slot_mapping and missing_indices
|
|
907
|
+
|
|
908
|
+
Returns:
|
|
909
|
+
set[int]: Set of block IDs that failed to load.
|
|
910
|
+
"""
|
|
911
|
+
|
|
912
|
+
if expected_mask.numel() == 0:
|
|
913
|
+
return set()
|
|
914
|
+
|
|
915
|
+
expected_mask_cpu = expected_mask.to(device="cpu", dtype=torch.bool)
|
|
916
|
+
ret_mask_cpu = ret_mask.to(device="cpu", dtype=torch.bool)
|
|
917
|
+
|
|
918
|
+
if ret_mask_cpu.shape[0] != expected_mask_cpu.shape[0]:
|
|
919
|
+
logger.debug("expected_mask_cpu.shape[0] != ret_mask_cpu.shape[0]")
|
|
920
|
+
return set()
|
|
921
|
+
|
|
922
|
+
missing_mask = expected_mask_cpu & ~ret_mask_cpu
|
|
923
|
+
if not torch.any(missing_mask):
|
|
924
|
+
return set()
|
|
925
|
+
|
|
926
|
+
missing_indices = torch.nonzero(missing_mask, as_tuple=False).view(-1)
|
|
927
|
+
if missing_indices.numel() == 0:
|
|
928
|
+
return set()
|
|
929
|
+
|
|
930
|
+
slot_mapping_cpu = slot_mapping.to(device="cpu", dtype=torch.long)
|
|
931
|
+
if slot_mapping_cpu.shape[0] > missing_mask.shape[0]:
|
|
932
|
+
slot_mapping_cpu = slot_mapping_cpu[: missing_mask.shape[0]]
|
|
933
|
+
|
|
934
|
+
missing_blocks_tensor = torch.unique(
|
|
935
|
+
slot_mapping_cpu[missing_indices] // self._block_size
|
|
936
|
+
)
|
|
937
|
+
missing_blocks = {int(block.item()) for block in missing_blocks_tensor}
|
|
938
|
+
|
|
939
|
+
if not missing_blocks:
|
|
940
|
+
return set()
|
|
941
|
+
|
|
942
|
+
logger.warning(
|
|
943
|
+
"Request %s failed to load %d tokens across %d blocks",
|
|
944
|
+
request_id,
|
|
945
|
+
missing_indices.numel(),
|
|
946
|
+
len(missing_blocks),
|
|
947
|
+
)
|
|
948
|
+
return missing_blocks
|
|
949
|
+
|
|
950
|
+
@_lmcache_nvtx_annotate
|
|
951
|
+
def wait_for_layer_load(self, layer_name: str) -> None:
|
|
952
|
+
"""Blocking until the KV for a specific layer is loaded into vLLM's
|
|
953
|
+
paged buffer.
|
|
954
|
+
|
|
955
|
+
This interface will be useful for layer-by-layer pipelining.
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
layer_name: the name of that layer
|
|
959
|
+
"""
|
|
960
|
+
if self.layerwise_retrievers:
|
|
961
|
+
logger.debug(f"Waiting for layer {self.current_layer} to be loaded")
|
|
962
|
+
|
|
963
|
+
# Wait for the layer to be loaded
|
|
964
|
+
for layerwise_retriever in self.layerwise_retrievers:
|
|
965
|
+
ret_token_mask = next(layerwise_retriever)
|
|
966
|
+
|
|
967
|
+
if self.current_layer == self.num_layers - 1:
|
|
968
|
+
assert ret_token_mask is not None
|
|
969
|
+
num_retrieved_tokens = ret_token_mask.sum().item()
|
|
970
|
+
logger.info(f"Retrieved {num_retrieved_tokens} tokens")
|
|
971
|
+
|
|
972
|
+
if self.layerwise_retrievers:
|
|
973
|
+
self.current_layer += 1
|
|
974
|
+
|
|
975
|
+
return
|
|
976
|
+
|
|
977
|
+
@_lmcache_nvtx_annotate
|
|
978
|
+
def save_kv_layer(
|
|
979
|
+
self,
|
|
980
|
+
layer_name: str,
|
|
981
|
+
kv_layer: torch.Tensor,
|
|
982
|
+
attn_metadata: "AttentionMetadata",
|
|
983
|
+
**kwargs,
|
|
984
|
+
) -> None:
|
|
985
|
+
"""Start saving the a layer of KV cache from vLLM's paged buffer
|
|
986
|
+
to the connector.
|
|
987
|
+
|
|
988
|
+
Args:
|
|
989
|
+
layer_name (str): the name of the layer.
|
|
990
|
+
kv_layer (torch.Tensor): the paged KV buffer of the current
|
|
991
|
+
layer in vLLM.
|
|
992
|
+
attn_metadata (AttentionMetadata): the attention metadata.
|
|
993
|
+
**kwargs: additional arguments for the save operation.
|
|
994
|
+
"""
|
|
995
|
+
assert self.lmcache_engine is not None
|
|
996
|
+
|
|
997
|
+
if not self.use_layerwise:
|
|
998
|
+
return
|
|
999
|
+
|
|
1000
|
+
if self.kv_role == "kv_consumer":
|
|
1001
|
+
# Don't do save if the role is kv_consumer
|
|
1002
|
+
return
|
|
1003
|
+
if self._parent._connector_metadata is None:
|
|
1004
|
+
logger.warning(
|
|
1005
|
+
"In connector.save_kv_layer, but the connector metadata is None"
|
|
1006
|
+
)
|
|
1007
|
+
return
|
|
1008
|
+
connector_metadata = self._parent._get_connector_metadata()
|
|
1009
|
+
assert isinstance(connector_metadata, LMCacheConnectorMetadata)
|
|
1010
|
+
|
|
1011
|
+
assert len(self.kv_caches) > 0
|
|
1012
|
+
|
|
1013
|
+
kvcaches = list(self.kv_caches.values())
|
|
1014
|
+
is_first = True
|
|
1015
|
+
|
|
1016
|
+
for request in connector_metadata.requests:
|
|
1017
|
+
save_spec = request.save_spec
|
|
1018
|
+
if (
|
|
1019
|
+
save_spec is None or not save_spec.can_save
|
|
1020
|
+
) and self.kv_role != "kv_producer":
|
|
1021
|
+
continue
|
|
1022
|
+
|
|
1023
|
+
layerwise_storer = self._layerwise_save_storers.get(request.req_id)
|
|
1024
|
+
if layerwise_storer is None:
|
|
1025
|
+
token_ids = request.token_ids
|
|
1026
|
+
assert isinstance(token_ids, list)
|
|
1027
|
+
|
|
1028
|
+
slot_mapping = request.slot_mapping
|
|
1029
|
+
assert isinstance(slot_mapping, torch.Tensor)
|
|
1030
|
+
assert len(slot_mapping) == len(token_ids)
|
|
1031
|
+
|
|
1032
|
+
# TODO: have a pre-allocated buffer to hold the slot_mappings
|
|
1033
|
+
slot_mapping = slot_mapping.to(self.device)
|
|
1034
|
+
|
|
1035
|
+
if self.kv_role == "kv_producer":
|
|
1036
|
+
skip_leading_tokens = 0
|
|
1037
|
+
else:
|
|
1038
|
+
assert save_spec is not None
|
|
1039
|
+
skip_leading_tokens = save_spec.skip_leading_tokens
|
|
1040
|
+
|
|
1041
|
+
if skip_leading_tokens == len(token_ids):
|
|
1042
|
+
continue # skip this request
|
|
1043
|
+
# Align to lmcache chunk size
|
|
1044
|
+
skip_leading_tokens = (
|
|
1045
|
+
skip_leading_tokens
|
|
1046
|
+
// self._lmcache_chunk_size
|
|
1047
|
+
* self._lmcache_chunk_size
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
store_mask = torch.ones(len(token_ids), dtype=torch.bool)
|
|
1051
|
+
store_mask[:skip_leading_tokens] = False
|
|
1052
|
+
|
|
1053
|
+
logger.debug(
|
|
1054
|
+
"Storing KV cache for %d out of %d tokens "
|
|
1055
|
+
"(skip_leading_tokens=%d) for request %s",
|
|
1056
|
+
len(token_ids) - skip_leading_tokens,
|
|
1057
|
+
len(token_ids),
|
|
1058
|
+
skip_leading_tokens,
|
|
1059
|
+
request.req_id,
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
# TODO (Jiayi): need to make layerwise storing
|
|
1063
|
+
# compatible with disagg spec
|
|
1064
|
+
layerwise_storer = self.lmcache_engine.store_layer(
|
|
1065
|
+
token_ids,
|
|
1066
|
+
mask=store_mask,
|
|
1067
|
+
kvcaches=kvcaches,
|
|
1068
|
+
slot_mapping=slot_mapping,
|
|
1069
|
+
offset=skip_leading_tokens,
|
|
1070
|
+
sync=is_first,
|
|
1071
|
+
req_id=request.req_id,
|
|
1072
|
+
)
|
|
1073
|
+
self._layerwise_save_storers[request.req_id] = layerwise_storer
|
|
1074
|
+
if is_first:
|
|
1075
|
+
is_first = False
|
|
1076
|
+
|
|
1077
|
+
next(layerwise_storer)
|
|
1078
|
+
|
|
1079
|
+
@_lmcache_nvtx_annotate
|
|
1080
|
+
def wait_for_save(self):
|
|
1081
|
+
"""Blocking until the KV cache is saved to the connector buffer."""
|
|
1082
|
+
|
|
1083
|
+
connector_metadata = self._parent._get_connector_metadata()
|
|
1084
|
+
assert isinstance(connector_metadata, LMCacheConnectorMetadata)
|
|
1085
|
+
|
|
1086
|
+
if self.kv_role == "kv_consumer":
|
|
1087
|
+
# Don't do save if the role is kv_consumer
|
|
1088
|
+
# But still need to unpin the kv caches according to req_id
|
|
1089
|
+
# to balance the pin count from contains()
|
|
1090
|
+
assert self.lmcache_engine is not None, (
|
|
1091
|
+
"LMCacheEngine must be initialized to unpin requests."
|
|
1092
|
+
)
|
|
1093
|
+
for request in connector_metadata.requests:
|
|
1094
|
+
self.lmcache_engine.lookup_unpin(request.req_id)
|
|
1095
|
+
|
|
1096
|
+
return
|
|
1097
|
+
|
|
1098
|
+
if self.use_layerwise:
|
|
1099
|
+
for request in connector_metadata.requests:
|
|
1100
|
+
layerwise_storer = self._layerwise_save_storers.pop(
|
|
1101
|
+
request.req_id, None
|
|
1102
|
+
)
|
|
1103
|
+
if layerwise_storer is not None:
|
|
1104
|
+
next(layerwise_storer)
|
|
1105
|
+
# unpin the kv caches according to req_id
|
|
1106
|
+
self.lmcache_engine.lookup_unpin(request.req_id)
|
|
1107
|
+
return
|
|
1108
|
+
|
|
1109
|
+
assert len(self.kv_caches) > 0
|
|
1110
|
+
kvcaches = list(self.kv_caches.values())
|
|
1111
|
+
|
|
1112
|
+
assert self.lmcache_engine is not None
|
|
1113
|
+
|
|
1114
|
+
for request in connector_metadata.requests:
|
|
1115
|
+
# unpin the kv caches according to req_id
|
|
1116
|
+
self.lmcache_engine.lookup_unpin(request.req_id)
|
|
1117
|
+
|
|
1118
|
+
save_spec = request.save_spec
|
|
1119
|
+
if (
|
|
1120
|
+
save_spec is None or not save_spec.can_save
|
|
1121
|
+
) and self.kv_role != "kv_producer":
|
|
1122
|
+
continue
|
|
1123
|
+
|
|
1124
|
+
token_ids = request.token_ids
|
|
1125
|
+
|
|
1126
|
+
slot_mapping = request.slot_mapping
|
|
1127
|
+
assert isinstance(slot_mapping, torch.Tensor)
|
|
1128
|
+
assert len(slot_mapping) == len(token_ids)
|
|
1129
|
+
|
|
1130
|
+
# TODO: have a pre-allocated buffer to hold the slot_mappings
|
|
1131
|
+
slot_mapping = slot_mapping.to(self.device)
|
|
1132
|
+
|
|
1133
|
+
skip_leading_tokens = save_spec.skip_leading_tokens
|
|
1134
|
+
# shared storage disaggregation will not have a disagg_spec passed in
|
|
1135
|
+
if self.kv_role == "kv_producer" and request.disagg_spec:
|
|
1136
|
+
skip_leading_tokens = min(
|
|
1137
|
+
skip_leading_tokens, request.disagg_spec.num_transferred_tokens
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
if skip_leading_tokens == len(token_ids):
|
|
1141
|
+
continue # skip this request
|
|
1142
|
+
# Align to lmcache chunk size
|
|
1143
|
+
skip_leading_tokens = (
|
|
1144
|
+
skip_leading_tokens
|
|
1145
|
+
// self._lmcache_chunk_size
|
|
1146
|
+
* self._lmcache_chunk_size
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
store_mask = torch.ones(len(token_ids), dtype=torch.bool)
|
|
1150
|
+
store_mask[:skip_leading_tokens] = False
|
|
1151
|
+
|
|
1152
|
+
logger.debug(
|
|
1153
|
+
"Storing KV cache for %d out of %d tokens "
|
|
1154
|
+
"(skip_leading_tokens=%d) for request %s",
|
|
1155
|
+
len(token_ids) - skip_leading_tokens,
|
|
1156
|
+
len(token_ids),
|
|
1157
|
+
skip_leading_tokens,
|
|
1158
|
+
request.req_id,
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
is_last_prefill = request.is_last_prefill
|
|
1162
|
+
if is_last_prefill:
|
|
1163
|
+
if request.disagg_spec:
|
|
1164
|
+
request.disagg_spec.is_last_prefill = True
|
|
1165
|
+
else:
|
|
1166
|
+
if not self.enable_blending:
|
|
1167
|
+
token_len = len(token_ids)
|
|
1168
|
+
aligned_token_len = (
|
|
1169
|
+
token_len // self._lmcache_chunk_size * self._lmcache_chunk_size
|
|
1170
|
+
)
|
|
1171
|
+
token_ids = token_ids[:aligned_token_len]
|
|
1172
|
+
store_mask = store_mask[:aligned_token_len]
|
|
1173
|
+
slot_mapping = slot_mapping[:aligned_token_len]
|
|
1174
|
+
|
|
1175
|
+
self.lmcache_engine.store(
|
|
1176
|
+
token_ids,
|
|
1177
|
+
mask=store_mask,
|
|
1178
|
+
kvcaches=kvcaches,
|
|
1179
|
+
slot_mapping=slot_mapping,
|
|
1180
|
+
offset=skip_leading_tokens,
|
|
1181
|
+
transfer_spec=request.disagg_spec,
|
|
1182
|
+
request_configs=request.request_configs,
|
|
1183
|
+
req_id=request.req_id,
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
# Update skip_leading_tokens only on last rank to ensure
|
|
1187
|
+
# each PP stage stores its own KV cache
|
|
1188
|
+
if get_pp_group().is_last_rank:
|
|
1189
|
+
# NOTE(Jiayi): We assume all tokens are saved
|
|
1190
|
+
save_spec.skip_leading_tokens = len(token_ids)
|
|
1191
|
+
if request.disagg_spec:
|
|
1192
|
+
request.disagg_spec.num_transferred_tokens = len(token_ids)
|
|
1193
|
+
|
|
1194
|
+
@_lmcache_nvtx_annotate
|
|
1195
|
+
def get_finished(
|
|
1196
|
+
self, finished_req_ids: set[str]
|
|
1197
|
+
) -> tuple[Optional[set[str]], Optional[set[str]]]:
|
|
1198
|
+
return None, None
|
|
1199
|
+
|
|
1200
|
+
def get_block_ids_with_load_errors(self) -> set[int]:
|
|
1201
|
+
invalid_blocks = self._invalid_block_ids.copy()
|
|
1202
|
+
self._invalid_block_ids.clear()
|
|
1203
|
+
return invalid_blocks
|
|
1204
|
+
|
|
1205
|
+
@_lmcache_nvtx_annotate
|
|
1206
|
+
def shutdown(self):
|
|
1207
|
+
"""Shutdown the connector by delegating to LMCacheManager."""
|
|
1208
|
+
logger.info("Starting LMCacheConnector shutdown...")
|
|
1209
|
+
self._manager.stop_services()
|
|
1210
|
+
|
|
1211
|
+
###################
|
|
1212
|
+
# Scheduler side APIs
|
|
1213
|
+
####################
|
|
1214
|
+
|
|
1215
|
+
@_lmcache_nvtx_annotate
|
|
1216
|
+
def get_num_new_matched_tokens(
|
|
1217
|
+
self,
|
|
1218
|
+
request: "Request",
|
|
1219
|
+
num_computed_tokens: int,
|
|
1220
|
+
) -> Optional[int]:
|
|
1221
|
+
"""
|
|
1222
|
+
Check for external KV cache hit.
|
|
1223
|
+
|
|
1224
|
+
Args:
|
|
1225
|
+
request (Request): the request object.
|
|
1226
|
+
num_computed_tokens (int): the number of locally
|
|
1227
|
+
computed tokens for this request
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
the number of tokens that can be loaded from the
|
|
1231
|
+
external KV cache beyond what is already computed.
|
|
1232
|
+
"""
|
|
1233
|
+
# Ignore DP attention mock requests
|
|
1234
|
+
if request.request_id.startswith("mock_req"):
|
|
1235
|
+
return 0
|
|
1236
|
+
# to handle preempted requests, we want `get_num_new_matched_tokens` to be
|
|
1237
|
+
# idempotent under the condition that `update_state_after_alloc` is NOT called
|
|
1238
|
+
# then the two side-effects that must be idempotent are:
|
|
1239
|
+
# 1. lookup_client caches a result
|
|
1240
|
+
# uncached in `update_state_after_alloc` if this request can be scheduled
|
|
1241
|
+
# 2. cache engine will pin the KV caches for the request
|
|
1242
|
+
# unpinned in `wait_for_save` if this request can be scheduled
|
|
1243
|
+
if self.kv_role == "kv_producer" and not hasattr(
|
|
1244
|
+
self.lookup_client, "supports_producer_reuse"
|
|
1245
|
+
):
|
|
1246
|
+
return 0
|
|
1247
|
+
|
|
1248
|
+
req_id = request.request_id
|
|
1249
|
+
|
|
1250
|
+
# lookup_client is always initialized for scheduler role
|
|
1251
|
+
assert self.lookup_client is not None
|
|
1252
|
+
|
|
1253
|
+
if (
|
|
1254
|
+
num_external_hit_tokens := self.lookup_client.lookup_cache(lookup_id=req_id)
|
|
1255
|
+
) != -1:
|
|
1256
|
+
# -1 means no result cached
|
|
1257
|
+
# None or int means ongoing (async) or cached result
|
|
1258
|
+
logger.debug(
|
|
1259
|
+
f"Found {num_external_hit_tokens} hit tokens for request"
|
|
1260
|
+
f" {req_id} in the lookup cache."
|
|
1261
|
+
)
|
|
1262
|
+
else:
|
|
1263
|
+
logger.debug(f"Looking up cache for the first time for request {req_id}!")
|
|
1264
|
+
self._requests_priority[req_id] = getattr(request, "priority", 0)
|
|
1265
|
+
|
|
1266
|
+
# token_ids = request.prompt_token_ids
|
|
1267
|
+
# all token ids covers the preemption case
|
|
1268
|
+
token_ids = request.all_token_ids
|
|
1269
|
+
|
|
1270
|
+
# If the request has multimodal hashes, apply them to the token ids
|
|
1271
|
+
mm_hashes, mm_positions = extract_mm_features(request)
|
|
1272
|
+
if mm_hashes and mm_positions:
|
|
1273
|
+
# TODO(Jiayi): Optimize this
|
|
1274
|
+
token_ids = torch.tensor(request.prompt_token_ids)
|
|
1275
|
+
apply_mm_hashes_to_token_ids(token_ids, mm_hashes, mm_positions)
|
|
1276
|
+
token_ids = token_ids.tolist()
|
|
1277
|
+
|
|
1278
|
+
request_configs = extract_request_configs(request.sampling_params)
|
|
1279
|
+
if self.skip_last_n_tokens > 0:
|
|
1280
|
+
token_ids = token_ids[: -self.skip_last_n_tokens]
|
|
1281
|
+
|
|
1282
|
+
num_external_hit_tokens = self.lookup_client.lookup(
|
|
1283
|
+
token_ids,
|
|
1284
|
+
lookup_id=req_id,
|
|
1285
|
+
request_configs=request_configs,
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
if num_external_hit_tokens is None:
|
|
1289
|
+
logger.debug(
|
|
1290
|
+
"Reqid: %s, Total tokens %d, Inference Engine computed tokens: %d, "
|
|
1291
|
+
"LMCache hit tokens: None.",
|
|
1292
|
+
req_id,
|
|
1293
|
+
request.num_tokens,
|
|
1294
|
+
num_computed_tokens,
|
|
1295
|
+
)
|
|
1296
|
+
return None
|
|
1297
|
+
|
|
1298
|
+
# When prompt length is divisible by the block size and all
|
|
1299
|
+
# blocks are cached, we need to recompute the last token.
|
|
1300
|
+
# This will be removed in the future if vLLM's scheduler provides
|
|
1301
|
+
# a better support for this case.
|
|
1302
|
+
need_to_allocate = num_external_hit_tokens - num_computed_tokens
|
|
1303
|
+
|
|
1304
|
+
# In, full-prompt-hit case, we need to recompute the last token
|
|
1305
|
+
if num_external_hit_tokens == request.num_tokens:
|
|
1306
|
+
need_to_allocate -= 1
|
|
1307
|
+
|
|
1308
|
+
# Check if hit tokens meet the minimum for retrieve
|
|
1309
|
+
# If below minimum, skip retrieve but still record hit tokens
|
|
1310
|
+
# for skip_leading_tokens to avoid re-storing existing chunks
|
|
1311
|
+
min_retrieve = self.config.min_retrieve_tokens
|
|
1312
|
+
below_min_retrieve = min_retrieve > 0 and need_to_allocate < min_retrieve
|
|
1313
|
+
|
|
1314
|
+
if below_min_retrieve:
|
|
1315
|
+
logger.info(
|
|
1316
|
+
"Reqid: %s, Total tokens %d, Inference Engine computed tokens: %d, "
|
|
1317
|
+
"LMCache hit tokens: %d, but need to load: %d < min_retrieve %d, "
|
|
1318
|
+
"skip retrieve but record for save skip",
|
|
1319
|
+
req_id,
|
|
1320
|
+
request.num_tokens,
|
|
1321
|
+
num_computed_tokens,
|
|
1322
|
+
num_external_hit_tokens,
|
|
1323
|
+
max(need_to_allocate, 0),
|
|
1324
|
+
min_retrieve,
|
|
1325
|
+
)
|
|
1326
|
+
else:
|
|
1327
|
+
logger.info(
|
|
1328
|
+
"Reqid: %s, Total tokens %d, Inference Engine computed tokens: %d, "
|
|
1329
|
+
"LMCache hit tokens: %d, need to load: %d",
|
|
1330
|
+
req_id,
|
|
1331
|
+
request.num_tokens,
|
|
1332
|
+
num_computed_tokens,
|
|
1333
|
+
num_external_hit_tokens,
|
|
1334
|
+
max(need_to_allocate, 0),
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
self.load_specs[req_id] = LoadSpec(
|
|
1338
|
+
vllm_cached_tokens=num_computed_tokens,
|
|
1339
|
+
lmcache_cached_tokens=num_external_hit_tokens,
|
|
1340
|
+
can_load=False,
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
if below_min_retrieve or need_to_allocate <= 0:
|
|
1344
|
+
return 0
|
|
1345
|
+
|
|
1346
|
+
# TODO: Align to vLLM block size. Should test whether it can be removed
|
|
1347
|
+
# need_to_allocate = need_to_allocate // self._block_size * \
|
|
1348
|
+
# self._block_size
|
|
1349
|
+
|
|
1350
|
+
return need_to_allocate
|
|
1351
|
+
|
|
1352
|
+
@_lmcache_nvtx_annotate
|
|
1353
|
+
def update_state_after_alloc(self, request: "Request", num_external_tokens: int):
|
|
1354
|
+
"""
|
|
1355
|
+
Update KVConnector state after temporary buffer alloc.
|
|
1356
|
+
|
|
1357
|
+
For SharedStorageConnector, update _request_needs_load
|
|
1358
|
+
if the CacheManager this allocated blocks for us.
|
|
1359
|
+
"""
|
|
1360
|
+
|
|
1361
|
+
# Clear local status in lookup client when a new request is
|
|
1362
|
+
# successfully scheduled.
|
|
1363
|
+
assert self.lookup_client is not None
|
|
1364
|
+
self.lookup_client.clear_lookup_status(request.request_id)
|
|
1365
|
+
|
|
1366
|
+
kv_transfer_params = (
|
|
1367
|
+
request.kv_transfer_params
|
|
1368
|
+
if hasattr(request, "kv_transfer_params")
|
|
1369
|
+
else None
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
if kv_transfer_params is not None and "disagg_spec" in kv_transfer_params:
|
|
1373
|
+
req_disagg_spec = kv_transfer_params["disagg_spec"]
|
|
1374
|
+
|
|
1375
|
+
receiver_id = req_disagg_spec["receiver_host"] + str(
|
|
1376
|
+
req_disagg_spec["receiver_init_port"]
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
disagg_spec = DisaggSpec(
|
|
1380
|
+
req_id=req_disagg_spec["req_id"],
|
|
1381
|
+
receiver_id=receiver_id,
|
|
1382
|
+
receiver_host=req_disagg_spec["receiver_host"],
|
|
1383
|
+
receiver_init_port=req_disagg_spec["receiver_init_port"],
|
|
1384
|
+
receiver_alloc_port=req_disagg_spec["receiver_alloc_port"],
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
tmp_disagg_tracker[request.request_id] = disagg_spec
|
|
1388
|
+
self._unfinished_requests[request.request_id] = request
|
|
1389
|
+
|
|
1390
|
+
if request.request_id not in self.load_specs:
|
|
1391
|
+
# No KV tokens from external KV cache, return
|
|
1392
|
+
return
|
|
1393
|
+
|
|
1394
|
+
if num_external_tokens == 0:
|
|
1395
|
+
# No need to load anything
|
|
1396
|
+
self.load_specs[request.request_id].can_load = False
|
|
1397
|
+
return
|
|
1398
|
+
|
|
1399
|
+
recalc_last = (
|
|
1400
|
+
1
|
|
1401
|
+
if (
|
|
1402
|
+
self.load_specs[request.request_id].lmcache_cached_tokens
|
|
1403
|
+
== request.num_tokens
|
|
1404
|
+
)
|
|
1405
|
+
else 0
|
|
1406
|
+
)
|
|
1407
|
+
assert (
|
|
1408
|
+
num_external_tokens
|
|
1409
|
+
== self.load_specs[request.request_id].lmcache_cached_tokens
|
|
1410
|
+
- self.load_specs[request.request_id].vllm_cached_tokens
|
|
1411
|
+
- recalc_last
|
|
1412
|
+
), (
|
|
1413
|
+
f"Mismatch in tokens to load: {num_external_tokens} vs "
|
|
1414
|
+
f"{self.load_specs[request.request_id].lmcache_cached_tokens} "
|
|
1415
|
+
"(tokens in lmcache) - "
|
|
1416
|
+
f"{self.load_specs[request.request_id].vllm_cached_tokens} "
|
|
1417
|
+
"(tokens in vllm) - "
|
|
1418
|
+
f"{recalc_last} "
|
|
1419
|
+
"(full lmcache hits subtracts last token to recalculate logits)"
|
|
1420
|
+
f" for request {request.request_id}"
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
self.load_specs[request.request_id].can_load = True
|
|
1424
|
+
|
|
1425
|
+
@_lmcache_nvtx_annotate
|
|
1426
|
+
def build_connector_meta(
|
|
1427
|
+
self, scheduler_output: SchedulerOutput
|
|
1428
|
+
) -> KVConnectorMetadata:
|
|
1429
|
+
"""Attach the connector metadata to the request object.
|
|
1430
|
+
|
|
1431
|
+
This function should NOT modify other fields in the scheduler_output
|
|
1432
|
+
except the `kv_connector_metadata` field.
|
|
1433
|
+
Also, calling this function will reset the state of the connector.
|
|
1434
|
+
|
|
1435
|
+
Args:
|
|
1436
|
+
scheduler_output (SchedulerOutput): the scheduler output object.
|
|
1437
|
+
"""
|
|
1438
|
+
|
|
1439
|
+
force_skip_save = self.kv_role == "kv_consumer" or self.force_skip_save
|
|
1440
|
+
|
|
1441
|
+
meta = LMCacheConnectorMetadata()
|
|
1442
|
+
|
|
1443
|
+
for finished_req_id in scheduler_output.finished_req_ids:
|
|
1444
|
+
self._request_trackers.pop(finished_req_id, None)
|
|
1445
|
+
self._unfinished_requests.pop(finished_req_id, None)
|
|
1446
|
+
|
|
1447
|
+
# We should load KV for:
|
|
1448
|
+
# 1. new requests
|
|
1449
|
+
# 2. preempted requests (once per recovery)
|
|
1450
|
+
# can_load will only be True if `update_state_after_alloc` has been called
|
|
1451
|
+
# which only happens when vLLM's KV manager has space to receive KV from LMCache
|
|
1452
|
+
for request in scheduler_output.scheduled_new_reqs:
|
|
1453
|
+
# Ignore DP attention mock requests
|
|
1454
|
+
if request.req_id.startswith("mock_req"):
|
|
1455
|
+
continue
|
|
1456
|
+
load_spec = self.load_specs.pop(request.req_id, None)
|
|
1457
|
+
num_tokens_to_compute = (
|
|
1458
|
+
request.num_computed_tokens
|
|
1459
|
+
+ scheduler_output.num_scheduled_tokens[request.req_id]
|
|
1460
|
+
)
|
|
1461
|
+
lmcache_cached_tokens = 0
|
|
1462
|
+
if load_spec is not None:
|
|
1463
|
+
lmcache_cached_tokens = load_spec.lmcache_cached_tokens
|
|
1464
|
+
request_priority = self._requests_priority.pop(request.req_id, 0)
|
|
1465
|
+
|
|
1466
|
+
skip_save = force_skip_save or (
|
|
1467
|
+
self.config.priority_limit is not None
|
|
1468
|
+
and request_priority > self.config.priority_limit
|
|
1469
|
+
)
|
|
1470
|
+
|
|
1471
|
+
request_tracker = RequestTracker.from_new_request(
|
|
1472
|
+
self.config,
|
|
1473
|
+
request,
|
|
1474
|
+
num_tokens_to_compute,
|
|
1475
|
+
lmcache_cached_tokens,
|
|
1476
|
+
skip_save,
|
|
1477
|
+
)
|
|
1478
|
+
self._request_trackers[request.req_id] = request_tracker
|
|
1479
|
+
|
|
1480
|
+
req_meta = ReqMeta.from_request_tracker(
|
|
1481
|
+
request_tracker,
|
|
1482
|
+
self._block_size,
|
|
1483
|
+
self._lmcache_chunk_size,
|
|
1484
|
+
load_spec=load_spec,
|
|
1485
|
+
discard_partial_chunks=self._discard_partial_chunks,
|
|
1486
|
+
save_decode_cache=self.config.save_decode_cache,
|
|
1487
|
+
)
|
|
1488
|
+
if req_meta is not None:
|
|
1489
|
+
meta.add_request(req_meta)
|
|
1490
|
+
|
|
1491
|
+
cached_reqs = scheduler_output.scheduled_cached_reqs
|
|
1492
|
+
|
|
1493
|
+
# NOTE: For backward compatibility with vllm version < 0.9.2,
|
|
1494
|
+
# In the latest vllm version, the type of scheduled_cached_reqs has
|
|
1495
|
+
# changed from list to object `CachedRequestData`
|
|
1496
|
+
if isinstance(cached_reqs, list):
|
|
1497
|
+
for i, req in enumerate(cached_reqs):
|
|
1498
|
+
load_spec = self.load_specs.pop(req.req_id, None)
|
|
1499
|
+
lmcache_cached_tokens = 0
|
|
1500
|
+
vllm_cached_tokens = 0
|
|
1501
|
+
if load_spec is not None:
|
|
1502
|
+
lmcache_cached_tokens = load_spec.lmcache_cached_tokens
|
|
1503
|
+
vllm_cached_tokens = load_spec.vllm_cached_tokens
|
|
1504
|
+
request_tracker = self._request_trackers[req.req_id]
|
|
1505
|
+
|
|
1506
|
+
# Pass all_token_ids for preempted requests to restore
|
|
1507
|
+
# token_ids correctly for chunk key computation
|
|
1508
|
+
all_token_ids = None
|
|
1509
|
+
if req.resumed_from_preemption:
|
|
1510
|
+
vllm_request = self._unfinished_requests.get(req.req_id)
|
|
1511
|
+
assert vllm_request is not None, (
|
|
1512
|
+
f"Preempted request {req.req_id} not found "
|
|
1513
|
+
"in _unfinished_requests"
|
|
1514
|
+
)
|
|
1515
|
+
all_token_ids = list(vllm_request.all_token_ids)
|
|
1516
|
+
|
|
1517
|
+
request_tracker.update(
|
|
1518
|
+
req.new_token_ids,
|
|
1519
|
+
req.new_block_ids,
|
|
1520
|
+
req.resumed_from_preemption,
|
|
1521
|
+
lmcache_cached_tokens=lmcache_cached_tokens,
|
|
1522
|
+
vllm_cached_tokens=vllm_cached_tokens,
|
|
1523
|
+
all_token_ids=all_token_ids,
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
req_meta = ReqMeta.from_request_tracker(
|
|
1527
|
+
request_tracker,
|
|
1528
|
+
self._block_size,
|
|
1529
|
+
self._lmcache_chunk_size,
|
|
1530
|
+
load_spec=load_spec,
|
|
1531
|
+
discard_partial_chunks=self._discard_partial_chunks,
|
|
1532
|
+
save_decode_cache=self.config.save_decode_cache,
|
|
1533
|
+
)
|
|
1534
|
+
if req_meta is not None:
|
|
1535
|
+
meta.add_request(req_meta)
|
|
1536
|
+
return meta
|
|
1537
|
+
|
|
1538
|
+
for i, req_id in enumerate(cached_reqs.req_ids):
|
|
1539
|
+
request_tracker = self._request_trackers[req_id]
|
|
1540
|
+
num_new_tokens = scheduler_output.num_scheduled_tokens[req_id]
|
|
1541
|
+
# TODO: this is a dangerous reference to the request object inside vllm
|
|
1542
|
+
if request := self._unfinished_requests.get(req_id):
|
|
1543
|
+
num_current_tokens = request.num_computed_tokens
|
|
1544
|
+
# tracker_len < num_computed_tokens during decode
|
|
1545
|
+
# (important for save_decode_cache).
|
|
1546
|
+
# num_computed_tokens < tracker_len after preemption.
|
|
1547
|
+
tracker_len = len(request_tracker.token_ids)
|
|
1548
|
+
slice_base = min(num_current_tokens, tracker_len)
|
|
1549
|
+
new_token_ids = request.all_token_ids[
|
|
1550
|
+
slice_base : slice_base + num_new_tokens
|
|
1551
|
+
]
|
|
1552
|
+
else:
|
|
1553
|
+
raise ValueError(
|
|
1554
|
+
f"Request {req_id} is not in _unfinished_requests, "
|
|
1555
|
+
f"but it is scheduled to be cached"
|
|
1556
|
+
)
|
|
1557
|
+
new_block_ids = cached_reqs.new_block_ids[i]
|
|
1558
|
+
|
|
1559
|
+
load_spec = self.load_specs.pop(req_id, None)
|
|
1560
|
+
lmcache_cached_tokens = 0
|
|
1561
|
+
vllm_cached_tokens = 0
|
|
1562
|
+
if load_spec is not None:
|
|
1563
|
+
lmcache_cached_tokens = load_spec.lmcache_cached_tokens
|
|
1564
|
+
vllm_cached_tokens = load_spec.vllm_cached_tokens
|
|
1565
|
+
|
|
1566
|
+
# Handle both old and new versions of CachedRequestData
|
|
1567
|
+
if hasattr(cached_reqs, "resumed_req_ids"):
|
|
1568
|
+
# New version with resumed_req_ids
|
|
1569
|
+
preempted = req_id in cached_reqs.resumed_req_ids
|
|
1570
|
+
elif hasattr(cached_reqs, "resumed_from_preemption"):
|
|
1571
|
+
# Old version with resumed_from_preemption
|
|
1572
|
+
preempted = cached_reqs.resumed_from_preemption[i]
|
|
1573
|
+
else:
|
|
1574
|
+
# This case should not be reached with supported vLLM versions.
|
|
1575
|
+
# Raising an error is safer than assuming not preempted.
|
|
1576
|
+
raise AttributeError(
|
|
1577
|
+
f"Unable to determine preemption status for request {req_id}. "
|
|
1578
|
+
f"This might be due to an unsupported vLLM version."
|
|
1579
|
+
)
|
|
1580
|
+
if preempted:
|
|
1581
|
+
assert load_spec is not None, (
|
|
1582
|
+
f"Request {req_id} is preempted but was not given a load spec"
|
|
1583
|
+
)
|
|
1584
|
+
# num_computed_tokens should be reset to 0 during preemption
|
|
1585
|
+
# and then set to the number of already cached tokens (maxxing
|
|
1586
|
+
# prefix caching and lmcache)
|
|
1587
|
+
# this assumption is crucial for the update() call of RequestTracker
|
|
1588
|
+
# On full cache hit, get_num_new_matched_tokens subtracts 1
|
|
1589
|
+
# to force last-token recomputation. This only affects
|
|
1590
|
+
# num_computed_tokens when lmcache has all tokens AND
|
|
1591
|
+
# provides more than vLLM's local cache.
|
|
1592
|
+
expected = max(lmcache_cached_tokens, load_spec.vllm_cached_tokens)
|
|
1593
|
+
full_hit_adj = (
|
|
1594
|
+
lmcache_cached_tokens == len(request.all_token_ids)
|
|
1595
|
+
and lmcache_cached_tokens > load_spec.vllm_cached_tokens
|
|
1596
|
+
)
|
|
1597
|
+
if full_hit_adj:
|
|
1598
|
+
expected -= 1
|
|
1599
|
+
assert request.num_computed_tokens == expected, (
|
|
1600
|
+
f"Preempted request {req_id} has "
|
|
1601
|
+
f"num_computed_tokens {request.num_computed_tokens} "
|
|
1602
|
+
f"but expected {expected} "
|
|
1603
|
+
f"(full_hit_adj={full_hit_adj})"
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
# When retrieve fail, vllm will call _handle_invalid_blocks to
|
|
1607
|
+
# reset request.num_computed_tokens, this will lead to
|
|
1608
|
+
# request_tracker.token_ids being not matched with vllm
|
|
1609
|
+
if num_current_tokens < len(request_tracker.token_ids):
|
|
1610
|
+
logger.warning(
|
|
1611
|
+
"Request %s rolled back from %d to %d tokens; "
|
|
1612
|
+
"truncating tracker state.",
|
|
1613
|
+
req_id,
|
|
1614
|
+
len(request_tracker.token_ids),
|
|
1615
|
+
num_current_tokens,
|
|
1616
|
+
)
|
|
1617
|
+
num_token_slots = (
|
|
1618
|
+
len(request_tracker.allocated_block_ids) * self._block_size
|
|
1619
|
+
)
|
|
1620
|
+
tokens_to_keep = num_current_tokens
|
|
1621
|
+
if num_token_slots < num_current_tokens:
|
|
1622
|
+
logger.warning(
|
|
1623
|
+
"Request %s tracker has %d token slots but %d tokens; "
|
|
1624
|
+
"capping token_ids to slot capacity.",
|
|
1625
|
+
req_id,
|
|
1626
|
+
num_token_slots,
|
|
1627
|
+
num_current_tokens,
|
|
1628
|
+
)
|
|
1629
|
+
tokens_to_keep = num_token_slots
|
|
1630
|
+
|
|
1631
|
+
request_tracker.token_ids = list(request.all_token_ids[:tokens_to_keep])
|
|
1632
|
+
request_tracker.num_saved_tokens = min(
|
|
1633
|
+
request_tracker.num_saved_tokens, tokens_to_keep
|
|
1634
|
+
)
|
|
1635
|
+
|
|
1636
|
+
# Pass all_token_ids for preempted requests to restore
|
|
1637
|
+
# token_ids correctly for chunk key computation
|
|
1638
|
+
all_token_ids = list(request.all_token_ids) if preempted else None
|
|
1639
|
+
|
|
1640
|
+
request_tracker.update(
|
|
1641
|
+
new_token_ids,
|
|
1642
|
+
new_block_ids,
|
|
1643
|
+
preempted=preempted,
|
|
1644
|
+
lmcache_cached_tokens=lmcache_cached_tokens,
|
|
1645
|
+
vllm_cached_tokens=vllm_cached_tokens,
|
|
1646
|
+
all_token_ids=all_token_ids,
|
|
1647
|
+
)
|
|
1648
|
+
|
|
1649
|
+
req_meta = ReqMeta.from_request_tracker(
|
|
1650
|
+
request_tracker,
|
|
1651
|
+
self._block_size,
|
|
1652
|
+
self._lmcache_chunk_size,
|
|
1653
|
+
load_spec=load_spec,
|
|
1654
|
+
discard_partial_chunks=self._discard_partial_chunks,
|
|
1655
|
+
save_decode_cache=self.config.save_decode_cache,
|
|
1656
|
+
)
|
|
1657
|
+
if req_meta is not None:
|
|
1658
|
+
meta.add_request(req_meta)
|
|
1659
|
+
|
|
1660
|
+
return meta
|
|
1661
|
+
|
|
1662
|
+
@_lmcache_nvtx_annotate
|
|
1663
|
+
def request_finished(
|
|
1664
|
+
self,
|
|
1665
|
+
request: "Request",
|
|
1666
|
+
block_ids: list[int],
|
|
1667
|
+
) -> tuple[bool, Optional[dict[str, Any]]]:
|
|
1668
|
+
# Layerwise save uses request-scoped generators. If request finishes
|
|
1669
|
+
# without entering wait_for_save (abort/error/evict path), make sure
|
|
1670
|
+
# we release the generator entry to avoid leaking state.
|
|
1671
|
+
if getattr(self, "use_layerwise", False) and hasattr(
|
|
1672
|
+
self, "_layerwise_save_storers"
|
|
1673
|
+
):
|
|
1674
|
+
self._layerwise_save_storers.pop(request.request_id, None)
|
|
1675
|
+
|
|
1676
|
+
# Cleanup if request was aborted
|
|
1677
|
+
if request.status == RequestStatus.FINISHED_ABORTED and self.async_loading:
|
|
1678
|
+
# Cancel any ongoing async lookup and prefetch tasks on workers
|
|
1679
|
+
lookup_id = request.request_id
|
|
1680
|
+
assert self.lookup_client is not None
|
|
1681
|
+
self.lookup_client.cancel_lookup(lookup_id) # type: ignore[attr-defined]
|
|
1682
|
+
|
|
1683
|
+
params = (
|
|
1684
|
+
request.kv_transfer_params
|
|
1685
|
+
if hasattr(request, "kv_transfer_params")
|
|
1686
|
+
else None
|
|
1687
|
+
)
|
|
1688
|
+
return_params = None
|
|
1689
|
+
|
|
1690
|
+
# NOTE: Used to stream back the first token
|
|
1691
|
+
# for disagg prefill
|
|
1692
|
+
if params is not None and "ret_first_tok" in params:
|
|
1693
|
+
return_params = {
|
|
1694
|
+
"first_tok": request._output_token_ids[0],
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if self.config.get_extra_config_value(
|
|
1698
|
+
"enable_cache_usage_details_in_response", False
|
|
1699
|
+
):
|
|
1700
|
+
request_tracker = self._request_trackers.get(request.request_id)
|
|
1701
|
+
if request_tracker:
|
|
1702
|
+
return_params = return_params or {}
|
|
1703
|
+
return_params["num_lmcache_cached_tokens"] = (
|
|
1704
|
+
request_tracker.num_lmcache_cached_tokens
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
return False, return_params
|
|
1708
|
+
|
|
1709
|
+
@_lmcache_nvtx_annotate
|
|
1710
|
+
def get_kv_events(self) -> Iterable[CacheStoreEvent]:
|
|
1711
|
+
if self.lmcache_engine is not None:
|
|
1712
|
+
return self.lmcache_engine.get_kv_events()
|
|
1713
|
+
return []
|