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,1906 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from typing import List, Optional, Tuple, Union
|
|
4
|
+
import abc
|
|
5
|
+
|
|
6
|
+
# Third Party
|
|
7
|
+
import torch
|
|
8
|
+
|
|
9
|
+
# First Party
|
|
10
|
+
from lmcache.logging import init_logger
|
|
11
|
+
from lmcache.utils import EngineType, _lmcache_nvtx_annotate
|
|
12
|
+
from lmcache.v1.compute.blend.utils import LMCBlenderBuilder
|
|
13
|
+
from lmcache.v1.gpu_connector.utils import (
|
|
14
|
+
LayoutHints,
|
|
15
|
+
assert_is_vllm_flash_attn_or_flash_infer,
|
|
16
|
+
assert_is_vllm_mla_or_flash_attn_or_flash_infer,
|
|
17
|
+
discover_gpu_kv_format,
|
|
18
|
+
ensure_contiguous_kv_caches,
|
|
19
|
+
get_block_size,
|
|
20
|
+
get_elements_per_layer,
|
|
21
|
+
get_head_size,
|
|
22
|
+
get_num_blocks,
|
|
23
|
+
get_page_buffer_size,
|
|
24
|
+
get_tokens_per_layer,
|
|
25
|
+
permute_kv_caches_to_contiguous,
|
|
26
|
+
)
|
|
27
|
+
from lmcache.v1.memory_management import GPUMemoryAllocator # noqa: E501
|
|
28
|
+
from lmcache.v1.memory_management import MemoryFormat, MemoryObj
|
|
29
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
30
|
+
import lmcache.c_ops as lmc_ops
|
|
31
|
+
|
|
32
|
+
logger = init_logger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GPUConnectorInterface(metaclass=abc.ABCMeta):
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
38
|
+
# FIXME (Yihua): We shouldn't put start and end here since
|
|
39
|
+
# it's not the responsibility of the GPUConnector to know
|
|
40
|
+
# the token-sequence-related information.
|
|
41
|
+
"""Store the data in the memory object into a GPU buffer.
|
|
42
|
+
Sub-classes should define the format of the kwargs.
|
|
43
|
+
|
|
44
|
+
:param MemoryObj memory_obj: The memory object to be copied into GPU.
|
|
45
|
+
:param int start: The starting index of the data in the corresponding
|
|
46
|
+
token sequence.
|
|
47
|
+
:param int end: The ending index of the data in the corresponding
|
|
48
|
+
token sequence.
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
@abc.abstractmethod
|
|
53
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
54
|
+
# FIXME (Yihua): We shouldn't put start and end here since
|
|
55
|
+
# it's not the responsibility of the GPUConnector to know
|
|
56
|
+
# the token-sequence-related information.
|
|
57
|
+
"""Load the data from a GPU buffer into the memory object.
|
|
58
|
+
Sub-classes should define the format of the kwargs.
|
|
59
|
+
|
|
60
|
+
:param MemoryObj memory_obj: The memory object to store the data from
|
|
61
|
+
GPU.
|
|
62
|
+
:param int start: The starting index of the data in the corresponding
|
|
63
|
+
token sequence.
|
|
64
|
+
:param int end: The ending index of the data in the corresponding
|
|
65
|
+
token sequence.
|
|
66
|
+
"""
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
|
|
69
|
+
@abc.abstractmethod
|
|
70
|
+
def batched_from_gpu(
|
|
71
|
+
self,
|
|
72
|
+
memory_objs: Union[List[List[MemoryObj]], List[MemoryObj]],
|
|
73
|
+
starts: List[int],
|
|
74
|
+
ends: List[int],
|
|
75
|
+
**kwargs,
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Batched load the data from a GPU memory into the memory objects.
|
|
79
|
+
Sub-classes should define the format of the kwargs.
|
|
80
|
+
|
|
81
|
+
:param Union[List[List[MemoryObj]], List[MemoryObj]] memory_obj:
|
|
82
|
+
The memory objects to store the data from GPU.
|
|
83
|
+
:param List[int] starts: The starting indices of the data in the corresponding
|
|
84
|
+
token sequence.
|
|
85
|
+
:param List[int] ends: The ending indices of the data in the corresponding
|
|
86
|
+
token sequence.
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
|
|
90
|
+
@abc.abstractmethod
|
|
91
|
+
def batched_to_gpu(
|
|
92
|
+
self,
|
|
93
|
+
memory_objs: Union[
|
|
94
|
+
List[List[MemoryObj]], List[MemoryObj], List[int], None
|
|
95
|
+
] = None,
|
|
96
|
+
starts: Optional[List[int]] = None,
|
|
97
|
+
ends: Optional[List[int]] = None,
|
|
98
|
+
**kwargs,
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Batched store the data from the memory objects to GPU kv cache.
|
|
102
|
+
Sub-classes should define the format of the kwargs.
|
|
103
|
+
|
|
104
|
+
For non-layerwise connectors:
|
|
105
|
+
:param Union[List[List[MemoryObj]], List[MemoryObj]] memory_obj:
|
|
106
|
+
The memory objects to store the data to GPU.
|
|
107
|
+
:param List[int] starts: The starting indices of the data in the corresponding
|
|
108
|
+
token sequence.
|
|
109
|
+
:param List[int] ends: The ending indices of the data in the corresponding
|
|
110
|
+
token sequence.
|
|
111
|
+
|
|
112
|
+
For layerwise connectors (generator pattern):
|
|
113
|
+
:param List[int] memory_objs: Actually the starts list
|
|
114
|
+
(positional compatibility)
|
|
115
|
+
:param List[int] starts: Actually the ends list
|
|
116
|
+
(positional compatibility)
|
|
117
|
+
Note: Layerwise connectors receive memory objects
|
|
118
|
+
via generator.send()
|
|
119
|
+
"""
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
|
|
122
|
+
@abc.abstractmethod
|
|
123
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
124
|
+
"""Get the shape of the data given the number of tokens."""
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
def initialize_kvcaches_ptr(self, **kwargs):
|
|
128
|
+
"""Initialize the kvcaches pointers if not already initialized."""
|
|
129
|
+
if "kvcaches" in kwargs:
|
|
130
|
+
self.kvcaches = kwargs["kvcaches"]
|
|
131
|
+
# Ensure contiguity on every call. HND tensors from vLLM have a
|
|
132
|
+
# non-contiguous logical view (NHD) that must be permuted back to
|
|
133
|
+
# the physical (HND) shape for correct kernel indexing.
|
|
134
|
+
# permute_kv_caches_to_contiguous is a no-op when already contiguous.
|
|
135
|
+
self.kvcaches = permute_kv_caches_to_contiguous(self.kvcaches)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class VLLMPagedMemGPUConnectorV2(GPUConnectorInterface):
|
|
139
|
+
"""
|
|
140
|
+
The GPU KV cache should be a nested tuple of K and V tensors.
|
|
141
|
+
More specifically, we have:
|
|
142
|
+
- GPUTensor = Tuple[KVLayer, ...]
|
|
143
|
+
- KVLayer = Tuple[Tensor, Tensor]
|
|
144
|
+
- Tensor: [num_blocks, block_size, num_heads, head_size]
|
|
145
|
+
|
|
146
|
+
It will produce / consume memory object with KV_2LTD format
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
hidden_dim_size: int,
|
|
152
|
+
num_layers: int,
|
|
153
|
+
use_gpu: bool = False,
|
|
154
|
+
**kwargs,
|
|
155
|
+
):
|
|
156
|
+
"""
|
|
157
|
+
If use_gpu is true, it will create a gpu intermediate buffer. In this
|
|
158
|
+
case, it requires the following kwargs:
|
|
159
|
+
- chunk_size: The MAX size of the chunk to be copied to GPU.
|
|
160
|
+
- dtype: The data type of the intermediate buffer.
|
|
161
|
+
"""
|
|
162
|
+
self.hidden_dim_size = hidden_dim_size
|
|
163
|
+
self.num_layers = num_layers
|
|
164
|
+
self.kv_cache_pointers = torch.empty(
|
|
165
|
+
num_layers, dtype=torch.int64, device="cpu"
|
|
166
|
+
)
|
|
167
|
+
# Not sure we need a dict here. Maybe a single GPU connector always
|
|
168
|
+
# works with a single device?
|
|
169
|
+
self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
|
|
170
|
+
|
|
171
|
+
self.kvcaches: Optional[List[torch.Tensor]] = None
|
|
172
|
+
|
|
173
|
+
self.gpu_buffer: Optional[torch.Tensor] = None
|
|
174
|
+
self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
|
|
175
|
+
self.layout_hints: LayoutHints = (
|
|
176
|
+
kwargs.get( # type: ignore[assignment]
|
|
177
|
+
"layout_hints"
|
|
178
|
+
)
|
|
179
|
+
or {}
|
|
180
|
+
)
|
|
181
|
+
if use_gpu:
|
|
182
|
+
assert "chunk_size" in kwargs, (
|
|
183
|
+
"chunk_size should be provided to create a GPU buffer."
|
|
184
|
+
)
|
|
185
|
+
assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
|
|
186
|
+
assert "device" in kwargs, (
|
|
187
|
+
"device should be provided to create a GPU buffer."
|
|
188
|
+
)
|
|
189
|
+
shape = self.get_shape(kwargs["chunk_size"])
|
|
190
|
+
self.gpu_buffer = torch.empty(
|
|
191
|
+
shape, dtype=kwargs["dtype"], device=kwargs["device"]
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self.store_stream = torch.cuda.Stream()
|
|
195
|
+
self.load_stream = torch.cuda.Stream()
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def from_metadata(
|
|
199
|
+
cls,
|
|
200
|
+
metadata: LMCacheMetadata,
|
|
201
|
+
use_gpu: bool = False,
|
|
202
|
+
device: Optional[torch.device] = None,
|
|
203
|
+
layout_hints: Optional[LayoutHints] = None,
|
|
204
|
+
) -> "VLLMPagedMemGPUConnectorV2":
|
|
205
|
+
"""Create a connector from LMCacheMetadata.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
metadata: The LMCache engine metadata containing model configuration.
|
|
209
|
+
use_gpu: Whether to use GPU intermediate buffer.
|
|
210
|
+
device: The device to use for the connector.
|
|
211
|
+
layout_hints: Optional hints about KV cache layout from the
|
|
212
|
+
serving engine.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
A new instance of VLLMPagedMemGPUConnectorV2.
|
|
216
|
+
"""
|
|
217
|
+
# Extract parameters from metadata
|
|
218
|
+
# kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
|
|
219
|
+
num_layers = metadata.kv_shape[0]
|
|
220
|
+
chunk_size = metadata.kv_shape[2]
|
|
221
|
+
num_kv_head = metadata.kv_shape[3]
|
|
222
|
+
head_size = metadata.kv_shape[4]
|
|
223
|
+
hidden_dim_size = num_kv_head * head_size
|
|
224
|
+
|
|
225
|
+
return cls(
|
|
226
|
+
hidden_dim_size=hidden_dim_size,
|
|
227
|
+
num_layers=num_layers,
|
|
228
|
+
use_gpu=use_gpu,
|
|
229
|
+
chunk_size=chunk_size,
|
|
230
|
+
dtype=metadata.kv_dtype,
|
|
231
|
+
device=device,
|
|
232
|
+
use_mla=metadata.use_mla,
|
|
233
|
+
layout_hints=layout_hints,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _initialize_pointers(self, kv_caches: List[torch.Tensor]) -> torch.Tensor:
|
|
237
|
+
self.device = kv_caches[0].device
|
|
238
|
+
assert self.device.type == "cuda", "The device should be CUDA."
|
|
239
|
+
idx = self.device.index
|
|
240
|
+
if idx in self.kv_cache_pointers_on_gpu:
|
|
241
|
+
return self.kv_cache_pointers_on_gpu[idx]
|
|
242
|
+
|
|
243
|
+
# contiguous before pointer capture or format discovery
|
|
244
|
+
kv_caches = ensure_contiguous_kv_caches(
|
|
245
|
+
kv_caches, kv_layout=self.layout_hints.get("kv_layout")
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
self.kv_cache_pointers.numpy()[:] = [t.data_ptr() for t in kv_caches]
|
|
249
|
+
self.kv_cache_pointers_on_gpu[idx] = torch.empty(
|
|
250
|
+
self.num_layers, dtype=torch.int64, device=self.device
|
|
251
|
+
)
|
|
252
|
+
self.kv_cache_pointers_on_gpu[idx].copy_(self.kv_cache_pointers)
|
|
253
|
+
|
|
254
|
+
self.gpu_kv_format = discover_gpu_kv_format(
|
|
255
|
+
kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
|
|
256
|
+
)
|
|
257
|
+
self.num_blocks = get_num_blocks(kv_caches, self.gpu_kv_format)
|
|
258
|
+
self.block_size = get_block_size(kv_caches, self.gpu_kv_format)
|
|
259
|
+
self.page_buffer_size = self.num_blocks * self.block_size
|
|
260
|
+
self.head_size = get_head_size(kv_caches, self.gpu_kv_format)
|
|
261
|
+
|
|
262
|
+
return self.kv_cache_pointers_on_gpu[idx]
|
|
263
|
+
|
|
264
|
+
@_lmcache_nvtx_annotate
|
|
265
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
266
|
+
"""Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
|
|
267
|
+
The kvcaches should correspond to the "WHOLE token sequence".
|
|
268
|
+
|
|
269
|
+
Note:
|
|
270
|
+
1. This function expects the 'slot_mapping' is a "full slot mapping"
|
|
271
|
+
where it's length is the same as the whole token sequence.
|
|
272
|
+
2. In the case that there is prefix caching, slot_mapping will starts
|
|
273
|
+
with -1s until the end of the matched prefix. The start and end
|
|
274
|
+
should NEVER overlap with the prefix caching (which means the
|
|
275
|
+
underlying CUDA kernel will never see -1 in slot_mapping)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
:raises ValueError: If 'kvcaches' is not provided in kwargs.
|
|
279
|
+
:raises AssertionError: If the memory object does not have a tensor.
|
|
280
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
281
|
+
"""
|
|
282
|
+
assert memory_obj.tensor is not None
|
|
283
|
+
|
|
284
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
285
|
+
|
|
286
|
+
assert self.kvcaches is not None, (
|
|
287
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if self.use_mla:
|
|
291
|
+
if memory_obj.metadata.fmt != MemoryFormat.KV_MLA_FMT:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
"The memory object should be in KV_MLA_FMT format in"
|
|
294
|
+
" order to be processed by VLLMPagedMemGPUConnector"
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
if memory_obj.metadata.fmt != MemoryFormat.KV_2LTD:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
"The memory object should be in KV_2LTD format in"
|
|
300
|
+
" order to be processed by VLLMPagedMemGPUConnector"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
if "slot_mapping" not in kwargs:
|
|
304
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
305
|
+
|
|
306
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
307
|
+
|
|
308
|
+
kv_cache_pointers = self._initialize_pointers(self.kvcaches)
|
|
309
|
+
|
|
310
|
+
# avoid read/write stream race condition for shared block
|
|
311
|
+
# this will only be potentially non-zero for the first
|
|
312
|
+
# block lmcache is transferring back
|
|
313
|
+
vllm_cached = kwargs.get("vllm_cached_tokens", 0)
|
|
314
|
+
skip_prefix_n_tokens = min(end - start, max(0, vllm_cached - start))
|
|
315
|
+
|
|
316
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
317
|
+
memory_obj.tensor,
|
|
318
|
+
kv_cache_pointers,
|
|
319
|
+
slot_mapping[start:end],
|
|
320
|
+
self.device,
|
|
321
|
+
self.page_buffer_size,
|
|
322
|
+
lmc_ops.TransferDirection.H2D,
|
|
323
|
+
self.gpu_kv_format,
|
|
324
|
+
block_size=self.block_size,
|
|
325
|
+
head_size=self.head_size,
|
|
326
|
+
skip_prefix_n_tokens=skip_prefix_n_tokens,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
@_lmcache_nvtx_annotate
|
|
330
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
331
|
+
"""Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
|
|
332
|
+
The kvcaches should correspond to the "WHOLE token sequence".
|
|
333
|
+
|
|
334
|
+
Will set the memory_obj.metadata.fmt to MemoryFormat.KV_2LTD.
|
|
335
|
+
|
|
336
|
+
Note:
|
|
337
|
+
1. This function expects the 'slot_mapping' is a "full slot mapping"
|
|
338
|
+
where it's length is the same as the whole token sequence.
|
|
339
|
+
2. In the case that there is prefix caching, slot_mapping will starts
|
|
340
|
+
with -1s until the end of the matched prefix. The start and end
|
|
341
|
+
should NEVER overlap with the prefix caching (which means the
|
|
342
|
+
underlying CUDA kernel will never see -1 in slot_mapping)
|
|
343
|
+
|
|
344
|
+
:raises ValueError: If 'kvcaches' is not provided in kwargs,
|
|
345
|
+
:raises AssertionError: If the memory object does not have a tensor.
|
|
346
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
347
|
+
"""
|
|
348
|
+
assert memory_obj.tensor is not None
|
|
349
|
+
|
|
350
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
351
|
+
assert self.kvcaches is not None, (
|
|
352
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if "slot_mapping" not in kwargs:
|
|
356
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
357
|
+
|
|
358
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
359
|
+
|
|
360
|
+
kv_cache_pointers = self._initialize_pointers(self.kvcaches)
|
|
361
|
+
|
|
362
|
+
with torch.cuda.stream(self.store_stream):
|
|
363
|
+
if self.gpu_buffer is None or end - start != self.gpu_buffer.shape[2]:
|
|
364
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
365
|
+
memory_obj.tensor,
|
|
366
|
+
kv_cache_pointers,
|
|
367
|
+
slot_mapping[start:end],
|
|
368
|
+
self.kvcaches[0].device,
|
|
369
|
+
self.page_buffer_size,
|
|
370
|
+
lmc_ops.TransferDirection.D2H,
|
|
371
|
+
self.gpu_kv_format,
|
|
372
|
+
block_size=self.block_size,
|
|
373
|
+
head_size=self.head_size,
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
377
|
+
assert self.gpu_buffer.device == self.kvcaches[0].device
|
|
378
|
+
tmp_gpu_buffer = self.gpu_buffer[:, :, : end - start, :]
|
|
379
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
380
|
+
tmp_gpu_buffer,
|
|
381
|
+
kv_cache_pointers,
|
|
382
|
+
slot_mapping[start:end],
|
|
383
|
+
self.kvcaches[0].device,
|
|
384
|
+
self.page_buffer_size,
|
|
385
|
+
lmc_ops.TransferDirection.D2H,
|
|
386
|
+
self.gpu_kv_format,
|
|
387
|
+
block_size=self.block_size,
|
|
388
|
+
head_size=self.head_size,
|
|
389
|
+
)
|
|
390
|
+
memory_obj.tensor.copy_(tmp_gpu_buffer, non_blocking=True)
|
|
391
|
+
|
|
392
|
+
if not memory_obj.tensor.is_cuda:
|
|
393
|
+
# Force a synchronize if the target buffer is NOT CUDA device
|
|
394
|
+
# NOTE: for better performance, we may not want to sync for every
|
|
395
|
+
# memory object
|
|
396
|
+
self.store_stream.synchronize()
|
|
397
|
+
|
|
398
|
+
if self.use_mla:
|
|
399
|
+
memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
|
|
400
|
+
|
|
401
|
+
# TODO(Jiayi): need to optimize to enable real batching
|
|
402
|
+
def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
403
|
+
with torch.cuda.stream(self.load_stream):
|
|
404
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
405
|
+
self.to_gpu(memory_obj, start, end, **kwargs)
|
|
406
|
+
self.load_stream.synchronize()
|
|
407
|
+
|
|
408
|
+
# TODO(Jiayi): need to optimize to enable real batching
|
|
409
|
+
def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
410
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
411
|
+
self.from_gpu(memory_obj, start, end, **kwargs)
|
|
412
|
+
|
|
413
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
414
|
+
kv_size = 1 if self.use_mla else 2
|
|
415
|
+
return torch.Size([kv_size, self.num_layers, num_tokens, self.hidden_dim_size])
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class VLLMPagedMemGPUConnectorV3(GPUConnectorInterface):
|
|
419
|
+
def __init__(
|
|
420
|
+
self,
|
|
421
|
+
metadata: LMCacheMetadata,
|
|
422
|
+
device: torch.device,
|
|
423
|
+
use_gpu: bool = False,
|
|
424
|
+
layout_hints: Optional[LayoutHints] = None,
|
|
425
|
+
):
|
|
426
|
+
assert device.type == "cuda", "The device should be CUDA."
|
|
427
|
+
self.metadata = metadata
|
|
428
|
+
self.device = device
|
|
429
|
+
self.use_mla = metadata.use_mla
|
|
430
|
+
self.chunk_size = metadata.chunk_size
|
|
431
|
+
self.use_gpu = use_gpu
|
|
432
|
+
self.layout_hints: LayoutHints = layout_hints or {}
|
|
433
|
+
self.kvcaches: Optional[List[torch.Tensor]] = None
|
|
434
|
+
|
|
435
|
+
self.init = False
|
|
436
|
+
self.group_kv_cache_pointers_on_gpu: Optional[list[torch.Tensor]] = None
|
|
437
|
+
self.group_tmp_buffer: Optional[list[torch.Tensor]] = None
|
|
438
|
+
|
|
439
|
+
self.store_stream = torch.cuda.Stream()
|
|
440
|
+
self.load_stream = torch.cuda.Stream()
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def from_metadata(
|
|
444
|
+
cls,
|
|
445
|
+
metadata: LMCacheMetadata,
|
|
446
|
+
use_gpu: bool = False,
|
|
447
|
+
device: Optional[torch.device] = None,
|
|
448
|
+
layout_hints: Optional[LayoutHints] = None,
|
|
449
|
+
) -> "VLLMPagedMemGPUConnectorV3":
|
|
450
|
+
assert device is not None
|
|
451
|
+
return cls(metadata, device, use_gpu, layout_hints=layout_hints)
|
|
452
|
+
|
|
453
|
+
def _initialize_kv_cache_pointers(self):
|
|
454
|
+
if self.init:
|
|
455
|
+
return
|
|
456
|
+
assert self.metadata.kv_layer_groups_manager.kv_layer_groups
|
|
457
|
+
|
|
458
|
+
# permute to contiguous before capturing pointers or doing format discovery
|
|
459
|
+
self.kvcaches = ensure_contiguous_kv_caches(
|
|
460
|
+
self.kvcaches, kv_layout=self.layout_hints.get("kv_layout")
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
if self.use_gpu:
|
|
464
|
+
# init tmp buffer
|
|
465
|
+
tmp_buf_shapes = self.metadata.get_shapes(self.chunk_size)
|
|
466
|
+
tmp_buf_dtypes = self.metadata.get_dtypes()
|
|
467
|
+
assert len(tmp_buf_shapes) == len(tmp_buf_dtypes)
|
|
468
|
+
self.group_tmp_buffer = [
|
|
469
|
+
torch.empty(tmp_buf_shape, dtype=tmp_buf_dtype, device=self.device)
|
|
470
|
+
for tmp_buf_shape, tmp_buf_dtype in zip(
|
|
471
|
+
tmp_buf_shapes, tmp_buf_dtypes, strict=True
|
|
472
|
+
)
|
|
473
|
+
]
|
|
474
|
+
self.group_kv_cache_pointers_on_gpu = []
|
|
475
|
+
for group in self.metadata.kv_layer_groups_manager.kv_layer_groups:
|
|
476
|
+
# init kv cache pointers
|
|
477
|
+
num_layers = group.num_layers
|
|
478
|
+
kv_cache_pointers = torch.empty(num_layers, dtype=torch.int64, device="cpu")
|
|
479
|
+
kv_cache_pointers.numpy()[:] = [
|
|
480
|
+
t.data_ptr()
|
|
481
|
+
for i, t in enumerate(self.kvcaches)
|
|
482
|
+
if i in group.layer_indices
|
|
483
|
+
]
|
|
484
|
+
kv_cache_pointers_on_gpu = torch.empty(
|
|
485
|
+
num_layers, dtype=torch.int64, device=self.device
|
|
486
|
+
)
|
|
487
|
+
kv_cache_pointers_on_gpu.copy_(kv_cache_pointers)
|
|
488
|
+
self.group_kv_cache_pointers_on_gpu.append(kv_cache_pointers_on_gpu)
|
|
489
|
+
|
|
490
|
+
self.gpu_kv_format = discover_gpu_kv_format(
|
|
491
|
+
self.kvcaches, EngineType.VLLM, layout_hints=self.layout_hints
|
|
492
|
+
)
|
|
493
|
+
self.num_blocks = get_num_blocks(self.kvcaches, self.gpu_kv_format)
|
|
494
|
+
self.block_size = get_block_size(self.kvcaches, self.gpu_kv_format)
|
|
495
|
+
self.page_buffer_size = self.num_blocks * self.block_size
|
|
496
|
+
self.head_size = get_head_size(self.kvcaches, self.gpu_kv_format)
|
|
497
|
+
|
|
498
|
+
self.init = True
|
|
499
|
+
logger.info("init kv cache pointers success in VLLMPagedMemGPUConnectorV3")
|
|
500
|
+
|
|
501
|
+
@_lmcache_nvtx_annotate
|
|
502
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
503
|
+
assert memory_obj.raw_tensor is not None
|
|
504
|
+
assert "slot_mapping" in kwargs
|
|
505
|
+
if self.use_mla:
|
|
506
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_MLA_FMT
|
|
507
|
+
else:
|
|
508
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_2LTD
|
|
509
|
+
|
|
510
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
511
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
512
|
+
assert self.kvcaches is not None
|
|
513
|
+
assert self.kvcaches[0].device == self.device
|
|
514
|
+
self._initialize_kv_cache_pointers()
|
|
515
|
+
assert self.group_kv_cache_pointers_on_gpu is not None
|
|
516
|
+
|
|
517
|
+
# avoid read/write stream race condition for shared block
|
|
518
|
+
# this will only be potentially non-zero for the first
|
|
519
|
+
# block lmcache is transferring back
|
|
520
|
+
vllm_cached = kwargs.get("vllm_cached_tokens", 0)
|
|
521
|
+
skip_prefix_n_tokens = min(end - start, max(0, vllm_cached - start))
|
|
522
|
+
|
|
523
|
+
for i, kv_cache_pointer in enumerate(self.group_kv_cache_pointers_on_gpu):
|
|
524
|
+
memory_obj_tensor = memory_obj.get_tensor(i)
|
|
525
|
+
assert memory_obj_tensor is not None
|
|
526
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
527
|
+
memory_obj_tensor,
|
|
528
|
+
kv_cache_pointer,
|
|
529
|
+
slot_mapping[start:end],
|
|
530
|
+
self.device,
|
|
531
|
+
self.page_buffer_size,
|
|
532
|
+
lmc_ops.TransferDirection.H2D,
|
|
533
|
+
self.gpu_kv_format,
|
|
534
|
+
block_size=self.block_size,
|
|
535
|
+
head_size=self.head_size,
|
|
536
|
+
skip_prefix_n_tokens=skip_prefix_n_tokens,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
@_lmcache_nvtx_annotate
|
|
540
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
541
|
+
assert memory_obj.raw_tensor is not None
|
|
542
|
+
assert "slot_mapping" in kwargs
|
|
543
|
+
|
|
544
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
545
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
546
|
+
assert self.kvcaches is not None
|
|
547
|
+
assert self.kvcaches[0].device == self.device
|
|
548
|
+
self._initialize_kv_cache_pointers()
|
|
549
|
+
assert self.group_kv_cache_pointers_on_gpu is not None
|
|
550
|
+
with torch.cuda.stream(self.store_stream):
|
|
551
|
+
if not self.use_gpu or end - start != self.chunk_size:
|
|
552
|
+
for i, kv_cache_pointer in enumerate(
|
|
553
|
+
self.group_kv_cache_pointers_on_gpu
|
|
554
|
+
):
|
|
555
|
+
memory_obj_tensor = memory_obj.get_tensor(i)
|
|
556
|
+
assert memory_obj_tensor is not None
|
|
557
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
558
|
+
memory_obj_tensor,
|
|
559
|
+
kv_cache_pointer,
|
|
560
|
+
slot_mapping[start:end],
|
|
561
|
+
self.device,
|
|
562
|
+
self.page_buffer_size,
|
|
563
|
+
lmc_ops.TransferDirection.D2H,
|
|
564
|
+
self.gpu_kv_format,
|
|
565
|
+
block_size=self.block_size,
|
|
566
|
+
head_size=self.head_size,
|
|
567
|
+
)
|
|
568
|
+
else:
|
|
569
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
570
|
+
assert self.group_tmp_buffer is not None
|
|
571
|
+
for i, kv_cache_pointer in enumerate(
|
|
572
|
+
self.group_kv_cache_pointers_on_gpu
|
|
573
|
+
):
|
|
574
|
+
tmp_gpu_buffer = self.group_tmp_buffer[i][:, :, : end - start, :]
|
|
575
|
+
lmc_ops.multi_layer_kv_transfer(
|
|
576
|
+
tmp_gpu_buffer,
|
|
577
|
+
kv_cache_pointer,
|
|
578
|
+
slot_mapping[start:end],
|
|
579
|
+
self.device,
|
|
580
|
+
self.page_buffer_size,
|
|
581
|
+
lmc_ops.TransferDirection.D2H,
|
|
582
|
+
self.gpu_kv_format,
|
|
583
|
+
block_size=self.block_size,
|
|
584
|
+
head_size=self.head_size,
|
|
585
|
+
)
|
|
586
|
+
memory_obj_tensor = memory_obj.get_tensor(i)
|
|
587
|
+
assert memory_obj_tensor is not None
|
|
588
|
+
memory_obj_tensor.copy_(tmp_gpu_buffer, non_blocking=True)
|
|
589
|
+
|
|
590
|
+
if not memory_obj.raw_tensor.is_cuda:
|
|
591
|
+
# Force a synchronize if the target buffer is NOT CUDA device
|
|
592
|
+
# NOTE: for better performance, we may not want to sync for every
|
|
593
|
+
# memory object
|
|
594
|
+
self.store_stream.synchronize()
|
|
595
|
+
|
|
596
|
+
if self.use_mla:
|
|
597
|
+
memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
|
|
598
|
+
|
|
599
|
+
def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
600
|
+
with torch.cuda.stream(self.load_stream):
|
|
601
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
602
|
+
self.to_gpu(memory_obj, start, end, **kwargs)
|
|
603
|
+
self.load_stream.synchronize()
|
|
604
|
+
|
|
605
|
+
def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
606
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
607
|
+
self.from_gpu(memory_obj, start, end, **kwargs)
|
|
608
|
+
|
|
609
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
610
|
+
raise NotImplementedError
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class VLLMBufferLayerwiseGPUConnector(GPUConnectorInterface):
|
|
614
|
+
def __init__(
|
|
615
|
+
self,
|
|
616
|
+
hidden_dim_size: int,
|
|
617
|
+
num_layers: int,
|
|
618
|
+
use_gpu: bool = False,
|
|
619
|
+
use_double_buffer: bool = True,
|
|
620
|
+
**kwargs,
|
|
621
|
+
):
|
|
622
|
+
self.hidden_dim_size = hidden_dim_size
|
|
623
|
+
self.num_layers = num_layers
|
|
624
|
+
|
|
625
|
+
self.kvcaches: Optional[List[torch.Tensor]] = None
|
|
626
|
+
self.layout_hints: LayoutHints = (
|
|
627
|
+
kwargs.get( # type: ignore[assignment]
|
|
628
|
+
"layout_hints"
|
|
629
|
+
)
|
|
630
|
+
or {}
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
# TODO(Jiayi): remove this hardcode
|
|
634
|
+
self.cache_positions = True
|
|
635
|
+
|
|
636
|
+
self.fused_rotary_emb = None
|
|
637
|
+
|
|
638
|
+
assert use_gpu, "use_gpu must be true in VLLMBufferLayerwiseGPUConnector"
|
|
639
|
+
assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
|
|
640
|
+
assert "device" in kwargs, "device should be provided to create a GPU buffer."
|
|
641
|
+
|
|
642
|
+
self.dtype = kwargs["dtype"]
|
|
643
|
+
self.device = kwargs["device"]
|
|
644
|
+
|
|
645
|
+
self.load_stream = torch.cuda.Stream()
|
|
646
|
+
self.store_stream = torch.cuda.Stream()
|
|
647
|
+
|
|
648
|
+
self.buffer_mapping: dict[int, MemoryObj] = {}
|
|
649
|
+
|
|
650
|
+
# track gap positions between blended chunks
|
|
651
|
+
self.current_gap_positions = None
|
|
652
|
+
|
|
653
|
+
self.use_gpu = use_gpu
|
|
654
|
+
self.gpu_buffer_allocator = None
|
|
655
|
+
self.element_size = torch.tensor([], dtype=self.dtype).element_size()
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def from_metadata(
|
|
659
|
+
cls,
|
|
660
|
+
metadata: LMCacheMetadata,
|
|
661
|
+
use_gpu: bool = False,
|
|
662
|
+
device: Optional[torch.device] = None,
|
|
663
|
+
layout_hints: Optional[LayoutHints] = None,
|
|
664
|
+
) -> "VLLMBufferLayerwiseGPUConnector":
|
|
665
|
+
"""Create a connector from LMCacheMetadata.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
metadata: The LMCache engine metadata containing model configuration.
|
|
669
|
+
use_gpu: Whether to use GPU intermediate buffer.
|
|
670
|
+
device: The device to use for the connector.
|
|
671
|
+
layout_hints: Optional hints about KV cache layout from the
|
|
672
|
+
serving engine.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
A new instance of VLLMBufferLayerwiseGPUConnector.
|
|
676
|
+
"""
|
|
677
|
+
# Extract parameters from metadata
|
|
678
|
+
# kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
|
|
679
|
+
num_layers = metadata.kv_shape[0]
|
|
680
|
+
num_kv_head = metadata.kv_shape[3]
|
|
681
|
+
head_size = metadata.kv_shape[4]
|
|
682
|
+
hidden_dim_size = num_kv_head * head_size
|
|
683
|
+
|
|
684
|
+
return cls(
|
|
685
|
+
hidden_dim_size=hidden_dim_size,
|
|
686
|
+
num_layers=num_layers,
|
|
687
|
+
use_gpu=use_gpu,
|
|
688
|
+
dtype=metadata.kv_dtype,
|
|
689
|
+
device=device,
|
|
690
|
+
layout_hints=layout_hints,
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
def _lazy_initialize_buffer(self, kv_caches):
|
|
694
|
+
"""
|
|
695
|
+
Lazily initialize the GPU buffer allocator if it is not initialized yet.
|
|
696
|
+
Currently, we use the `kv_caches` (kv cache pointer) to determine
|
|
697
|
+
the gpu buffer size in gpu connector.
|
|
698
|
+
Also, the first request might be a bit slower due to buffer creation.
|
|
699
|
+
"""
|
|
700
|
+
if self.use_gpu and self.gpu_buffer_allocator is None:
|
|
701
|
+
logger.info("Lazily initializing GPU buffer.")
|
|
702
|
+
# NOTE (Jiayi): We use the first layer to determine the gpu buffer size.
|
|
703
|
+
# NOTE (Jiayi): Using the exact number of tokens in the first layer
|
|
704
|
+
# is okay since fragmentation shouldn't exist in the `gpu_buffer_allocator`
|
|
705
|
+
# in layerwise mode.
|
|
706
|
+
|
|
707
|
+
kv_caches = ensure_contiguous_kv_caches(
|
|
708
|
+
kv_caches, kv_layout=self.layout_hints.get("kv_layout")
|
|
709
|
+
)
|
|
710
|
+
self.kvcaches = kv_caches
|
|
711
|
+
self.gpu_kv_format = discover_gpu_kv_format(
|
|
712
|
+
kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
|
|
713
|
+
)
|
|
714
|
+
assert_is_vllm_flash_attn_or_flash_infer(self.gpu_kv_format)
|
|
715
|
+
self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
|
|
716
|
+
self.elements_per_layer = get_elements_per_layer(
|
|
717
|
+
kv_caches, self.gpu_kv_format
|
|
718
|
+
)
|
|
719
|
+
logger.info(
|
|
720
|
+
f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
|
|
721
|
+
)
|
|
722
|
+
gpu_buffer_size = self.elements_per_layer * self.element_size
|
|
723
|
+
self.gpu_buffer_allocator = GPUMemoryAllocator(
|
|
724
|
+
gpu_buffer_size, device=self.device
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
def get_kv(self, layer_id: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
728
|
+
"""
|
|
729
|
+
Get the KV cache for the given layer ID.
|
|
730
|
+
This function is used to get the KV cache from the GPU buffer.
|
|
731
|
+
"""
|
|
732
|
+
if layer_id not in self.buffer_mapping:
|
|
733
|
+
raise ValueError(f"Layer {layer_id} is not loaded into GPU buffer.")
|
|
734
|
+
|
|
735
|
+
gpu_buffer = self.buffer_mapping[layer_id].tensor
|
|
736
|
+
assert gpu_buffer is not None
|
|
737
|
+
return gpu_buffer[0], gpu_buffer[1]
|
|
738
|
+
|
|
739
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
740
|
+
""" """
|
|
741
|
+
|
|
742
|
+
raise NotImplementedError
|
|
743
|
+
|
|
744
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
745
|
+
""" """
|
|
746
|
+
|
|
747
|
+
raise NotImplementedError
|
|
748
|
+
|
|
749
|
+
@_lmcache_nvtx_annotate
|
|
750
|
+
def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
|
|
751
|
+
"""
|
|
752
|
+
This function is a generator that moves the KV cache from the memory
|
|
753
|
+
objects to buffer GPU memory. In each iteration i, it (1) loads the KV
|
|
754
|
+
cache of layer i from CPU -> GPU buffer, (2) recovers the positional
|
|
755
|
+
encoding of the layer i-1's KV cache in the GPU buffer, and (3)
|
|
756
|
+
moves the KV cache of layer i-2 from GPU buffer to paged GPU memory.
|
|
757
|
+
In total, this the generator will yield num_layers + 2 times.
|
|
758
|
+
|
|
759
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
760
|
+
token sequence.
|
|
761
|
+
|
|
762
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
763
|
+
token sequence.
|
|
764
|
+
"""
|
|
765
|
+
|
|
766
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
767
|
+
assert self.kvcaches is not None, (
|
|
768
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
if "slot_mapping" not in kwargs:
|
|
772
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
773
|
+
|
|
774
|
+
if self.fused_rotary_emb is None and self.cache_positions:
|
|
775
|
+
# TODO(Jiayi): Make this more elegant
|
|
776
|
+
# First Party
|
|
777
|
+
from lmcache.integration.vllm.utils import ENGINE_NAME
|
|
778
|
+
|
|
779
|
+
self.lmc_model = LMCBlenderBuilder.get(ENGINE_NAME).layerwise_model
|
|
780
|
+
self.fused_rotary_emb = self.lmc_model.fused_rotary_emb
|
|
781
|
+
|
|
782
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
783
|
+
|
|
784
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
785
|
+
|
|
786
|
+
num_all_tokens = ends[-1] - starts[0]
|
|
787
|
+
slot_mapping_full = slot_mapping[starts[0] : ends[-1]]
|
|
788
|
+
|
|
789
|
+
# compute gap positions
|
|
790
|
+
gap_mask = torch.ones(
|
|
791
|
+
num_all_tokens, dtype=torch.bool, device=slot_mapping_full.device
|
|
792
|
+
)
|
|
793
|
+
buf_offset = starts[0]
|
|
794
|
+
|
|
795
|
+
for start, end in zip(starts, ends, strict=False):
|
|
796
|
+
gap_mask[start - buf_offset : end - buf_offset] = False
|
|
797
|
+
|
|
798
|
+
self.current_gap_positions = torch.where(gap_mask)[0]
|
|
799
|
+
|
|
800
|
+
buf_offset = starts[0]
|
|
801
|
+
if self.cache_positions:
|
|
802
|
+
new_positions_full = torch.arange(
|
|
803
|
+
starts[0], ends[-1], dtype=torch.int64, device=self.kvcaches[0].device
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
buffer_shape = self.get_shape(num_all_tokens)
|
|
807
|
+
assert self.gpu_buffer_allocator is not None
|
|
808
|
+
compute_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
809
|
+
buffer_shape, self.dtype, MemoryFormat.KV_2TD
|
|
810
|
+
)
|
|
811
|
+
load_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
812
|
+
buffer_shape, self.dtype, MemoryFormat.KV_2TD
|
|
813
|
+
)
|
|
814
|
+
assert compute_gpu_buffer_obj is not None, (
|
|
815
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
816
|
+
)
|
|
817
|
+
assert load_gpu_buffer_obj is not None, (
|
|
818
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
819
|
+
)
|
|
820
|
+
assert compute_gpu_buffer_obj.tensor is not None
|
|
821
|
+
assert load_gpu_buffer_obj.tensor is not None
|
|
822
|
+
|
|
823
|
+
# current_stream = torch.cuda.current_stream()
|
|
824
|
+
|
|
825
|
+
if self.cache_positions:
|
|
826
|
+
old_positions_full = torch.zeros(
|
|
827
|
+
(num_all_tokens,), dtype=torch.int64, device=self.kvcaches[0].device
|
|
828
|
+
)
|
|
829
|
+
for layer_id in range(self.num_layers + 2):
|
|
830
|
+
if layer_id > 1:
|
|
831
|
+
lmc_ops.single_layer_kv_transfer(
|
|
832
|
+
self.buffer_mapping[layer_id - 2].tensor,
|
|
833
|
+
self.kvcaches[layer_id - 2],
|
|
834
|
+
slot_mapping_full,
|
|
835
|
+
lmc_ops.TransferDirection.H2D,
|
|
836
|
+
self.gpu_kv_format,
|
|
837
|
+
token_major=False, # shape is [2, num_tokens, hidden_dim]
|
|
838
|
+
)
|
|
839
|
+
del self.buffer_mapping[layer_id - 2]
|
|
840
|
+
|
|
841
|
+
logger.debug(f"Finished loading layer {layer_id - 2} into paged memory")
|
|
842
|
+
|
|
843
|
+
if layer_id > 0 and layer_id <= self.num_layers:
|
|
844
|
+
# NOTE: wait until both compute and load streams are done
|
|
845
|
+
torch.cuda.synchronize()
|
|
846
|
+
|
|
847
|
+
# ping-pong the buffers
|
|
848
|
+
compute_gpu_buffer_obj, load_gpu_buffer_obj = (
|
|
849
|
+
load_gpu_buffer_obj,
|
|
850
|
+
compute_gpu_buffer_obj,
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
if self.cache_positions:
|
|
854
|
+
assert compute_gpu_buffer_obj.tensor is not None
|
|
855
|
+
|
|
856
|
+
compute_gpu_buffer_obj.tensor[0] = self.fused_rotary_emb(
|
|
857
|
+
old_positions_full,
|
|
858
|
+
new_positions_full,
|
|
859
|
+
compute_gpu_buffer_obj.tensor[0],
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
# gap zeroing after RoPE
|
|
863
|
+
if self.current_gap_positions.numel():
|
|
864
|
+
compute_gpu_buffer_obj.tensor[:, self.current_gap_positions] = 0.0
|
|
865
|
+
|
|
866
|
+
self.buffer_mapping[layer_id - 1] = compute_gpu_buffer_obj
|
|
867
|
+
|
|
868
|
+
logger.debug(f"Finished loading layer {layer_id - 1} into buffer")
|
|
869
|
+
|
|
870
|
+
if layer_id < self.num_layers:
|
|
871
|
+
memory_objs_layer = yield
|
|
872
|
+
|
|
873
|
+
# memobj -> gpu_buffer
|
|
874
|
+
with torch.cuda.stream(self.load_stream):
|
|
875
|
+
for start, end, memory_obj in zip(
|
|
876
|
+
starts, ends, memory_objs_layer, strict=False
|
|
877
|
+
):
|
|
878
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_2TD
|
|
879
|
+
assert load_gpu_buffer_obj.tensor is not None
|
|
880
|
+
load_gpu_buffer_obj.tensor[0][
|
|
881
|
+
start - buf_offset : end - buf_offset
|
|
882
|
+
].copy_(memory_obj.tensor[0], non_blocking=True)
|
|
883
|
+
|
|
884
|
+
load_gpu_buffer_obj.tensor[1][
|
|
885
|
+
start - buf_offset : end - buf_offset
|
|
886
|
+
].copy_(memory_obj.tensor[1], non_blocking=True)
|
|
887
|
+
|
|
888
|
+
if self.cache_positions and layer_id == 0:
|
|
889
|
+
old_positions_full[
|
|
890
|
+
start - buf_offset : end - buf_offset
|
|
891
|
+
] = memory_obj.metadata.cached_positions
|
|
892
|
+
|
|
893
|
+
elif layer_id == self.num_layers:
|
|
894
|
+
yield
|
|
895
|
+
|
|
896
|
+
# free the buffer memory
|
|
897
|
+
load_gpu_buffer_obj.ref_count_down()
|
|
898
|
+
compute_gpu_buffer_obj.ref_count_down()
|
|
899
|
+
|
|
900
|
+
assert len(self.buffer_mapping) == 0, (
|
|
901
|
+
"There are still layers in the buffer mapping after "
|
|
902
|
+
"releasing the GPU buffers."
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
yield
|
|
906
|
+
|
|
907
|
+
# TODO(Jiayi): Reduce repetitive operations in `batched_to_gpu`
|
|
908
|
+
# and `batched_from_gpu`.
|
|
909
|
+
@_lmcache_nvtx_annotate
|
|
910
|
+
def batched_from_gpu(
|
|
911
|
+
self,
|
|
912
|
+
memory_objs: Union[List[List[MemoryObj]], List[MemoryObj]],
|
|
913
|
+
starts: List[int],
|
|
914
|
+
ends: List[int],
|
|
915
|
+
**kwargs,
|
|
916
|
+
):
|
|
917
|
+
"""
|
|
918
|
+
This function is a generator that moves the KV cache from the paged GPU
|
|
919
|
+
memory to the memory objects. The first iteration will prepare some
|
|
920
|
+
related metadata and initiate the transfer in the first layer. In each
|
|
921
|
+
of the following iterations, it will first wait until the storing of
|
|
922
|
+
previous layer finishes, and then initiate string the KV cache of the
|
|
923
|
+
current layer one. The storing process of the KV cache is paged GPU
|
|
924
|
+
memory -> GPU buffer -> memory objects. The last iteration simply waits
|
|
925
|
+
for the last layer to finish.
|
|
926
|
+
In total, this the generator will yield num_layers + 1 times.
|
|
927
|
+
|
|
928
|
+
:param memory_objs: The memory objects to store the KV cache. The first
|
|
929
|
+
dimension is the number of layers, and the second dimension is the
|
|
930
|
+
number of memory objects (i.e., number of chunks) for each layer.
|
|
931
|
+
|
|
932
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
933
|
+
token sequence.
|
|
934
|
+
|
|
935
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
936
|
+
token sequence.
|
|
937
|
+
|
|
938
|
+
:raises ValueError: If 'kvcaches' is not provided in kwargs.
|
|
939
|
+
|
|
940
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
941
|
+
"""
|
|
942
|
+
|
|
943
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
944
|
+
assert self.kvcaches is not None, (
|
|
945
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
if "slot_mapping" not in kwargs:
|
|
949
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
950
|
+
|
|
951
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
952
|
+
|
|
953
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
954
|
+
|
|
955
|
+
buf_start = 0
|
|
956
|
+
slot_mapping_chunks = []
|
|
957
|
+
buf_starts_ends = []
|
|
958
|
+
old_positions_chunks = []
|
|
959
|
+
for start, end in zip(starts, ends, strict=False):
|
|
960
|
+
buf_end = buf_start + end - start
|
|
961
|
+
buf_starts_ends.append((buf_start, buf_end))
|
|
962
|
+
slot_mapping_chunks.append(slot_mapping[start:end])
|
|
963
|
+
buf_start = buf_end
|
|
964
|
+
if self.cache_positions:
|
|
965
|
+
old_positions_chunks.append(
|
|
966
|
+
torch.arange(
|
|
967
|
+
start, end, device=self.kvcaches[0].device, dtype=torch.int64
|
|
968
|
+
)
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
|
|
972
|
+
|
|
973
|
+
num_tokens = len(slot_mapping_full)
|
|
974
|
+
buffer_shape = self.get_shape(num_tokens)
|
|
975
|
+
assert self.gpu_buffer_allocator is not None
|
|
976
|
+
tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
977
|
+
buffer_shape, self.dtype, MemoryFormat.KV_2TD
|
|
978
|
+
)
|
|
979
|
+
assert tmp_gpu_buffer_obj is not None, (
|
|
980
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
981
|
+
)
|
|
982
|
+
assert tmp_gpu_buffer_obj.tensor is not None
|
|
983
|
+
|
|
984
|
+
current_stream = torch.cuda.current_stream()
|
|
985
|
+
|
|
986
|
+
for layer_id in range(self.num_layers):
|
|
987
|
+
memory_objs_layer = memory_objs[layer_id]
|
|
988
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
989
|
+
with torch.cuda.stream(self.store_stream):
|
|
990
|
+
self.store_stream.wait_stream(current_stream)
|
|
991
|
+
lmc_ops.single_layer_kv_transfer(
|
|
992
|
+
tmp_gpu_buffer_obj.tensor,
|
|
993
|
+
self.kvcaches[layer_id],
|
|
994
|
+
slot_mapping_full,
|
|
995
|
+
lmc_ops.TransferDirection.D2H,
|
|
996
|
+
self.gpu_kv_format,
|
|
997
|
+
token_major=False, # shape is [2, num_tokens, hidden_dim]
|
|
998
|
+
)
|
|
999
|
+
for (buf_start, buf_end), memory_obj, old_positions in zip(
|
|
1000
|
+
buf_starts_ends,
|
|
1001
|
+
memory_objs_layer,
|
|
1002
|
+
old_positions_chunks,
|
|
1003
|
+
strict=False,
|
|
1004
|
+
):
|
|
1005
|
+
assert memory_obj.tensor is not None
|
|
1006
|
+
memory_obj.tensor[0].copy_(
|
|
1007
|
+
tmp_gpu_buffer_obj.tensor[0][buf_start:buf_end],
|
|
1008
|
+
non_blocking=True,
|
|
1009
|
+
)
|
|
1010
|
+
memory_obj.tensor[1].copy_(
|
|
1011
|
+
tmp_gpu_buffer_obj.tensor[1][buf_start:buf_end],
|
|
1012
|
+
non_blocking=True,
|
|
1013
|
+
)
|
|
1014
|
+
if self.cache_positions:
|
|
1015
|
+
memory_obj.metadata.cached_positions = old_positions
|
|
1016
|
+
|
|
1017
|
+
yield
|
|
1018
|
+
self.store_stream.synchronize()
|
|
1019
|
+
logger.debug(f"Finished offloading layer {layer_id}")
|
|
1020
|
+
|
|
1021
|
+
# free the buffer memory
|
|
1022
|
+
tmp_gpu_buffer_obj.ref_count_down()
|
|
1023
|
+
yield
|
|
1024
|
+
|
|
1025
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
1026
|
+
return torch.Size([2, num_tokens, self.hidden_dim_size])
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
class VLLMPagedMemLayerwiseGPUConnector(GPUConnectorInterface):
|
|
1030
|
+
""" """
|
|
1031
|
+
|
|
1032
|
+
def __init__(
|
|
1033
|
+
self,
|
|
1034
|
+
hidden_dim_size: int,
|
|
1035
|
+
num_layers: int,
|
|
1036
|
+
use_gpu: bool = False,
|
|
1037
|
+
**kwargs,
|
|
1038
|
+
):
|
|
1039
|
+
self.hidden_dim_size = hidden_dim_size
|
|
1040
|
+
self.num_layers = num_layers
|
|
1041
|
+
self.use_gpu = use_gpu
|
|
1042
|
+
self.layout_hints: LayoutHints = (
|
|
1043
|
+
kwargs.get( # type: ignore[assignment]
|
|
1044
|
+
"layout_hints"
|
|
1045
|
+
)
|
|
1046
|
+
or {}
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
self.gpu_buffer_allocator = None
|
|
1050
|
+
|
|
1051
|
+
assert "chunk_size" in kwargs, (
|
|
1052
|
+
"chunk_size should be provided to create a GPU buffer."
|
|
1053
|
+
)
|
|
1054
|
+
assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
|
|
1055
|
+
assert "device" in kwargs, "device should be provided to create a GPU buffer."
|
|
1056
|
+
|
|
1057
|
+
self.dtype = kwargs["dtype"]
|
|
1058
|
+
self.device = kwargs["device"]
|
|
1059
|
+
|
|
1060
|
+
self.kvcaches: Optional[List[torch.Tensor]] = None
|
|
1061
|
+
|
|
1062
|
+
# All sizes are in bytes
|
|
1063
|
+
self.element_size = torch.tensor([], dtype=self.dtype).element_size()
|
|
1064
|
+
|
|
1065
|
+
self.load_stream = torch.cuda.Stream()
|
|
1066
|
+
self.store_stream = torch.cuda.Stream()
|
|
1067
|
+
|
|
1068
|
+
self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
|
|
1069
|
+
|
|
1070
|
+
@classmethod
|
|
1071
|
+
def from_metadata(
|
|
1072
|
+
cls,
|
|
1073
|
+
metadata: LMCacheMetadata,
|
|
1074
|
+
use_gpu: bool = False,
|
|
1075
|
+
device: Optional[torch.device] = None,
|
|
1076
|
+
layout_hints: Optional[LayoutHints] = None,
|
|
1077
|
+
) -> "VLLMPagedMemLayerwiseGPUConnector":
|
|
1078
|
+
"""Create a connector from LMCacheMetadata.
|
|
1079
|
+
|
|
1080
|
+
Args:
|
|
1081
|
+
metadata: The LMCache engine metadata containing model configuration.
|
|
1082
|
+
use_gpu: Whether to use GPU intermediate buffer.
|
|
1083
|
+
device: The device to use for the connector.
|
|
1084
|
+
layout_hints: Optional hints about KV cache layout from the
|
|
1085
|
+
serving engine.
|
|
1086
|
+
|
|
1087
|
+
Returns:
|
|
1088
|
+
A new instance of VLLMPagedMemLayerwiseGPUConnector.
|
|
1089
|
+
"""
|
|
1090
|
+
# Extract parameters from metadata
|
|
1091
|
+
# kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
|
|
1092
|
+
num_layers = metadata.kv_shape[0]
|
|
1093
|
+
chunk_size = metadata.kv_shape[2]
|
|
1094
|
+
num_kv_head = metadata.kv_shape[3]
|
|
1095
|
+
head_size = metadata.kv_shape[4]
|
|
1096
|
+
hidden_dim_size = num_kv_head * head_size
|
|
1097
|
+
|
|
1098
|
+
return cls(
|
|
1099
|
+
hidden_dim_size=hidden_dim_size,
|
|
1100
|
+
num_layers=num_layers,
|
|
1101
|
+
use_gpu=use_gpu,
|
|
1102
|
+
chunk_size=chunk_size,
|
|
1103
|
+
dtype=metadata.kv_dtype,
|
|
1104
|
+
device=device,
|
|
1105
|
+
use_mla=metadata.use_mla,
|
|
1106
|
+
layout_hints=layout_hints,
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
def _lazy_initialize_buffer(self, kv_caches):
|
|
1110
|
+
"""
|
|
1111
|
+
Lazily initialize the GPU buffer allocator if it is not initialized yet.
|
|
1112
|
+
Currently, we use the `kv_caches` (kv cache pointer) to determine
|
|
1113
|
+
the gpu buffer size in gpu connector.
|
|
1114
|
+
Also, the first request might be a bit slower due to buffer creation.
|
|
1115
|
+
"""
|
|
1116
|
+
if self.use_gpu and self.gpu_buffer_allocator is None:
|
|
1117
|
+
logger.info("Lazily initializing GPU buffer.")
|
|
1118
|
+
# NOTE (Jiayi): We use the first layer to determine the gpu buffer size.
|
|
1119
|
+
# NOTE (Jiayi): Using the exact number of tokens in the first layer
|
|
1120
|
+
# is okay since fragmentation shouldn't exist in the `gpu_buffer_allocator`
|
|
1121
|
+
# in layerwise mode.
|
|
1122
|
+
|
|
1123
|
+
kv_caches = ensure_contiguous_kv_caches(
|
|
1124
|
+
kv_caches, kv_layout=self.layout_hints.get("kv_layout")
|
|
1125
|
+
)
|
|
1126
|
+
self.kvcaches = kv_caches
|
|
1127
|
+
self.gpu_kv_format = discover_gpu_kv_format(
|
|
1128
|
+
kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
|
|
1129
|
+
)
|
|
1130
|
+
assert_is_vllm_mla_or_flash_attn_or_flash_infer(self.gpu_kv_format)
|
|
1131
|
+
self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
|
|
1132
|
+
self.elements_per_layer = get_elements_per_layer(
|
|
1133
|
+
kv_caches, self.gpu_kv_format
|
|
1134
|
+
)
|
|
1135
|
+
logger.info(
|
|
1136
|
+
f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
|
|
1137
|
+
)
|
|
1138
|
+
gpu_buffer_size = self.elements_per_layer * self.element_size
|
|
1139
|
+
self.gpu_buffer_allocator = GPUMemoryAllocator(
|
|
1140
|
+
gpu_buffer_size, device=self.device
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1144
|
+
""" """
|
|
1145
|
+
|
|
1146
|
+
raise NotImplementedError
|
|
1147
|
+
|
|
1148
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1149
|
+
""" """
|
|
1150
|
+
|
|
1151
|
+
raise NotImplementedError
|
|
1152
|
+
|
|
1153
|
+
@_lmcache_nvtx_annotate
|
|
1154
|
+
def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
|
|
1155
|
+
"""
|
|
1156
|
+
This function is a generator that moves the KV cache from the memory
|
|
1157
|
+
objects to paged GPU memory. The first iteration will prepare some
|
|
1158
|
+
related metadata. In each of the following iterations, it will first
|
|
1159
|
+
wait until the loading of the previous layer finish, and then load
|
|
1160
|
+
one layer of KV cache from the memory objects -> GPU buffer ->
|
|
1161
|
+
paged GPU memory. The last iteration simply waits for the last layer
|
|
1162
|
+
to finish.
|
|
1163
|
+
In total, this the generator will yield num_layers + 2 times.
|
|
1164
|
+
|
|
1165
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
1166
|
+
token sequence.
|
|
1167
|
+
|
|
1168
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
1169
|
+
token sequence.
|
|
1170
|
+
|
|
1171
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1172
|
+
"""
|
|
1173
|
+
|
|
1174
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
1175
|
+
assert self.kvcaches is not None, (
|
|
1176
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
if "slot_mapping" not in kwargs:
|
|
1180
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1181
|
+
|
|
1182
|
+
if "sync" not in kwargs:
|
|
1183
|
+
raise ValueError("'sync' should be provided in kwargs.")
|
|
1184
|
+
|
|
1185
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1186
|
+
sync: bool = kwargs["sync"]
|
|
1187
|
+
|
|
1188
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
1189
|
+
|
|
1190
|
+
slot_mapping_chunks = []
|
|
1191
|
+
for start, end in zip(starts, ends, strict=False):
|
|
1192
|
+
slot_mapping_chunks.append(slot_mapping[start:end])
|
|
1193
|
+
|
|
1194
|
+
# TODO(Jiayi): Optimize away this `cat`
|
|
1195
|
+
slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
|
|
1196
|
+
|
|
1197
|
+
num_tokens = len(slot_mapping_full)
|
|
1198
|
+
|
|
1199
|
+
mem_fmt = MemoryFormat.KV_MLA_FMT if self.use_mla else MemoryFormat.KV_T2D
|
|
1200
|
+
|
|
1201
|
+
tmp_gpu_buffer_obj: Optional[MemoryObj] = None
|
|
1202
|
+
if self.use_gpu:
|
|
1203
|
+
buffer_shape = self.get_shape(num_tokens)
|
|
1204
|
+
assert self.gpu_buffer_allocator is not None
|
|
1205
|
+
tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
1206
|
+
buffer_shape, self.dtype, mem_fmt
|
|
1207
|
+
)
|
|
1208
|
+
assert tmp_gpu_buffer_obj is not None, (
|
|
1209
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
1210
|
+
)
|
|
1211
|
+
assert tmp_gpu_buffer_obj.tensor is not None
|
|
1212
|
+
|
|
1213
|
+
offset = starts[0]
|
|
1214
|
+
current_stream = torch.cuda.current_stream()
|
|
1215
|
+
|
|
1216
|
+
for layer_id in range(self.num_layers):
|
|
1217
|
+
memory_objs_layer = yield
|
|
1218
|
+
if sync:
|
|
1219
|
+
current_stream.wait_stream(self.load_stream)
|
|
1220
|
+
if layer_id > 0:
|
|
1221
|
+
logger.debug(f"Finished loading layer {layer_id - 1}")
|
|
1222
|
+
|
|
1223
|
+
# memobj -> gpu_buffer -> kvcaches
|
|
1224
|
+
with torch.cuda.stream(self.load_stream):
|
|
1225
|
+
for start, end, memory_obj in zip(
|
|
1226
|
+
starts, ends, memory_objs_layer, strict=False
|
|
1227
|
+
):
|
|
1228
|
+
# Validate memory format
|
|
1229
|
+
if self.use_mla:
|
|
1230
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_MLA_FMT, (
|
|
1231
|
+
f"Expected memory format {MemoryFormat.KV_MLA_FMT}, "
|
|
1232
|
+
f"got {memory_obj.metadata.fmt}"
|
|
1233
|
+
)
|
|
1234
|
+
else:
|
|
1235
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_T2D, (
|
|
1236
|
+
f"Expected memory format {MemoryFormat.KV_T2D}, "
|
|
1237
|
+
f"got {memory_obj.metadata.fmt}"
|
|
1238
|
+
)
|
|
1239
|
+
if self.use_gpu:
|
|
1240
|
+
tmp_gpu_buffer_obj.tensor[start - offset : end - offset].copy_(
|
|
1241
|
+
memory_obj.tensor, non_blocking=True
|
|
1242
|
+
)
|
|
1243
|
+
else:
|
|
1244
|
+
lmc_ops.single_layer_kv_transfer(
|
|
1245
|
+
memory_obj.tensor,
|
|
1246
|
+
self.kvcaches[layer_id],
|
|
1247
|
+
slot_mapping_full,
|
|
1248
|
+
lmc_ops.TransferDirection.H2D,
|
|
1249
|
+
self.gpu_kv_format,
|
|
1250
|
+
token_major=True,
|
|
1251
|
+
)
|
|
1252
|
+
|
|
1253
|
+
if self.use_gpu:
|
|
1254
|
+
lmc_ops.single_layer_kv_transfer(
|
|
1255
|
+
tmp_gpu_buffer_obj.tensor,
|
|
1256
|
+
self.kvcaches[layer_id],
|
|
1257
|
+
slot_mapping_full,
|
|
1258
|
+
lmc_ops.TransferDirection.H2D,
|
|
1259
|
+
self.gpu_kv_format,
|
|
1260
|
+
token_major=True,
|
|
1261
|
+
)
|
|
1262
|
+
yield
|
|
1263
|
+
|
|
1264
|
+
# synchronize the last layer
|
|
1265
|
+
if sync:
|
|
1266
|
+
current_stream.wait_stream(self.load_stream)
|
|
1267
|
+
|
|
1268
|
+
# free the buffer memory
|
|
1269
|
+
if tmp_gpu_buffer_obj is not None:
|
|
1270
|
+
tmp_gpu_buffer_obj.ref_count_down()
|
|
1271
|
+
|
|
1272
|
+
logger.debug(f"Finished loading all {self.num_layers} layers.")
|
|
1273
|
+
yield
|
|
1274
|
+
|
|
1275
|
+
@_lmcache_nvtx_annotate
|
|
1276
|
+
def batched_from_gpu(
|
|
1277
|
+
self,
|
|
1278
|
+
memory_objs: Union[List[List[MemoryObj]]],
|
|
1279
|
+
starts: List[int],
|
|
1280
|
+
ends: List[int],
|
|
1281
|
+
**kwargs,
|
|
1282
|
+
):
|
|
1283
|
+
"""
|
|
1284
|
+
This function is a generator that moves the KV cache from the paged GPU
|
|
1285
|
+
memory to the memory objects. The first iteration will prepare some
|
|
1286
|
+
related metadata and initiate the transfer in the first layer. In each
|
|
1287
|
+
of the following iterations, it will first wait until the storing of
|
|
1288
|
+
previous layer finishes, and then initiate string the KV cache of the
|
|
1289
|
+
current layer one. The storing process of the KV cache is paged GPU
|
|
1290
|
+
memory -> GPU buffer -> memory objects. The last iteration simply waits
|
|
1291
|
+
for the last layer to finish.
|
|
1292
|
+
In total, this the generator will yield num_layers + 1 times.
|
|
1293
|
+
|
|
1294
|
+
:param memory_objs: The memory objects to store the KV cache. The first
|
|
1295
|
+
dimension is the number of layers, and the second dimension is the
|
|
1296
|
+
number of memory objects (i.e., number of chunks) for each layer.
|
|
1297
|
+
|
|
1298
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
1299
|
+
token sequence.
|
|
1300
|
+
|
|
1301
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
1302
|
+
token sequence.
|
|
1303
|
+
|
|
1304
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1305
|
+
"""
|
|
1306
|
+
|
|
1307
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
1308
|
+
assert self.kvcaches is not None, (
|
|
1309
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
if "slot_mapping" not in kwargs:
|
|
1313
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1314
|
+
|
|
1315
|
+
if "sync" not in kwargs:
|
|
1316
|
+
raise ValueError("'sync' should be provided in kwargs.")
|
|
1317
|
+
|
|
1318
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1319
|
+
sync: bool = kwargs["sync"]
|
|
1320
|
+
|
|
1321
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
1322
|
+
|
|
1323
|
+
slot_mapping_chunks = []
|
|
1324
|
+
for start, end in zip(starts, ends, strict=False):
|
|
1325
|
+
slot_mapping_chunks.append(slot_mapping[start:end])
|
|
1326
|
+
|
|
1327
|
+
slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
|
|
1328
|
+
|
|
1329
|
+
num_tokens = len(slot_mapping_full)
|
|
1330
|
+
|
|
1331
|
+
mem_fmt = MemoryFormat.KV_MLA_FMT if self.use_mla else MemoryFormat.KV_T2D
|
|
1332
|
+
|
|
1333
|
+
tmp_gpu_buffer_obj: Optional[MemoryObj] = None
|
|
1334
|
+
if self.use_gpu:
|
|
1335
|
+
buffer_shape = self.get_shape(num_tokens)
|
|
1336
|
+
assert self.gpu_buffer_allocator is not None
|
|
1337
|
+
tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
1338
|
+
buffer_shape, self.dtype, mem_fmt
|
|
1339
|
+
)
|
|
1340
|
+
assert tmp_gpu_buffer_obj is not None, (
|
|
1341
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
1342
|
+
)
|
|
1343
|
+
assert tmp_gpu_buffer_obj.tensor is not None
|
|
1344
|
+
|
|
1345
|
+
offset = starts[0]
|
|
1346
|
+
current_stream = torch.cuda.current_stream()
|
|
1347
|
+
|
|
1348
|
+
for layer_id in range(self.num_layers):
|
|
1349
|
+
memory_objs_layer = memory_objs[layer_id]
|
|
1350
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
1351
|
+
with torch.cuda.stream(self.store_stream):
|
|
1352
|
+
self.store_stream.wait_stream(current_stream)
|
|
1353
|
+
if self.use_gpu:
|
|
1354
|
+
lmc_ops.single_layer_kv_transfer(
|
|
1355
|
+
tmp_gpu_buffer_obj.tensor,
|
|
1356
|
+
self.kvcaches[layer_id],
|
|
1357
|
+
slot_mapping_full,
|
|
1358
|
+
lmc_ops.TransferDirection.D2H,
|
|
1359
|
+
self.gpu_kv_format,
|
|
1360
|
+
token_major=True,
|
|
1361
|
+
)
|
|
1362
|
+
for start, end, memory_obj in zip(
|
|
1363
|
+
starts, ends, memory_objs_layer, strict=False
|
|
1364
|
+
):
|
|
1365
|
+
assert memory_obj.tensor is not None
|
|
1366
|
+
if self.use_gpu:
|
|
1367
|
+
memory_obj.tensor.copy_(
|
|
1368
|
+
tmp_gpu_buffer_obj.tensor[start - offset : end - offset],
|
|
1369
|
+
non_blocking=True,
|
|
1370
|
+
)
|
|
1371
|
+
else:
|
|
1372
|
+
lmc_ops.single_layer_kv_transfer(
|
|
1373
|
+
memory_obj.tensor,
|
|
1374
|
+
self.kvcaches[layer_id],
|
|
1375
|
+
slot_mapping[start:end],
|
|
1376
|
+
lmc_ops.TransferDirection.D2H,
|
|
1377
|
+
self.gpu_kv_format,
|
|
1378
|
+
token_major=True,
|
|
1379
|
+
)
|
|
1380
|
+
# Set metadata format
|
|
1381
|
+
if self.use_mla:
|
|
1382
|
+
memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
|
|
1383
|
+
|
|
1384
|
+
yield
|
|
1385
|
+
if sync:
|
|
1386
|
+
self.store_stream.synchronize()
|
|
1387
|
+
logger.debug(f"Finished offloading layer {layer_id}")
|
|
1388
|
+
|
|
1389
|
+
# free the buffer memory
|
|
1390
|
+
if tmp_gpu_buffer_obj is not None:
|
|
1391
|
+
tmp_gpu_buffer_obj.ref_count_down()
|
|
1392
|
+
|
|
1393
|
+
yield
|
|
1394
|
+
|
|
1395
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
1396
|
+
if self.use_mla:
|
|
1397
|
+
# MLA format: [num_tokens, hidden_dim_size]
|
|
1398
|
+
return torch.Size([num_tokens, self.hidden_dim_size])
|
|
1399
|
+
else:
|
|
1400
|
+
# Standard format: [num_tokens, 2, hidden_dim_size]
|
|
1401
|
+
return torch.Size([num_tokens, 2, self.hidden_dim_size])
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
class SGLangGPUConnector(GPUConnectorInterface):
|
|
1405
|
+
"""
|
|
1406
|
+
The GPU KV cache should be a list of tensors, one for each layer,
|
|
1407
|
+
with separate key and value pointers.
|
|
1408
|
+
More specifically, we have:
|
|
1409
|
+
- kvcaches: Tuple[List[Tensor], List[Tensor]]
|
|
1410
|
+
- The first element is a list of key tensors, one per layer.
|
|
1411
|
+
- The second element is a list of value tensors, one per layer.
|
|
1412
|
+
- Each tensor: [page_buffer_size, head_num, head_size]
|
|
1413
|
+
|
|
1414
|
+
The connector manages the transfer of KV cache data between CPU and GPU
|
|
1415
|
+
memory for SGLang using pointer arrays for efficient access.
|
|
1416
|
+
It will produce/consume memory objects with KV_2LTD format.
|
|
1417
|
+
"""
|
|
1418
|
+
|
|
1419
|
+
def __init__(
|
|
1420
|
+
self, hidden_dim_size: int, num_layers: int, use_gpu: bool = False, **kwargs
|
|
1421
|
+
):
|
|
1422
|
+
self.hidden_dim_size = hidden_dim_size
|
|
1423
|
+
self.num_layers = num_layers
|
|
1424
|
+
|
|
1425
|
+
self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
|
|
1426
|
+
self.page_buffer_size = 0
|
|
1427
|
+
|
|
1428
|
+
self.gpu_buffer: Optional[torch.Tensor] = None
|
|
1429
|
+
self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
|
|
1430
|
+
|
|
1431
|
+
self.num_kv_cache = num_layers if self.use_mla else num_layers * 2
|
|
1432
|
+
self.kv_cache_pointers = torch.empty(
|
|
1433
|
+
self.num_kv_cache, dtype=torch.int64, device="cpu"
|
|
1434
|
+
)
|
|
1435
|
+
|
|
1436
|
+
if use_gpu:
|
|
1437
|
+
assert "chunk_size" in kwargs, (
|
|
1438
|
+
"chunk_size should be provided to create a GPU buffer."
|
|
1439
|
+
)
|
|
1440
|
+
assert "device" in kwargs, (
|
|
1441
|
+
"device should be provided to create a GPU buffer."
|
|
1442
|
+
)
|
|
1443
|
+
shape = self.get_shape(kwargs["chunk_size"])
|
|
1444
|
+
self.gpu_buffer = torch.empty(
|
|
1445
|
+
shape, dtype=kwargs["dtype"], device=kwargs["device"]
|
|
1446
|
+
)
|
|
1447
|
+
logger.info(f"GPU buffer: {self.gpu_buffer.shape}")
|
|
1448
|
+
|
|
1449
|
+
def _initialize_pointers(self, kv_caches: List[torch.Tensor]) -> torch.Tensor:
|
|
1450
|
+
# Discover format first to handle flattening correctly
|
|
1451
|
+
self.gpu_kv_format = discover_gpu_kv_format(kv_caches, EngineType.SGLANG)
|
|
1452
|
+
|
|
1453
|
+
# For TWO_X_NL_X_NBBS_NH_HS format, kv_caches is [[k_list], [v_list]]
|
|
1454
|
+
# We need to flatten it to [k0, k1, ..., v0, v1, ...]
|
|
1455
|
+
if self.gpu_kv_format == lmc_ops.GPUKVFormat.TWO_X_NL_X_NBBS_NH_HS:
|
|
1456
|
+
flat_kv_caches = kv_caches[0] + kv_caches[1] # [k_list] + [v_list]
|
|
1457
|
+
device = flat_kv_caches[0].device
|
|
1458
|
+
else:
|
|
1459
|
+
flat_kv_caches = kv_caches
|
|
1460
|
+
device = flat_kv_caches[0].device
|
|
1461
|
+
|
|
1462
|
+
assert len(flat_kv_caches) == self.num_kv_cache, (
|
|
1463
|
+
f"Expected {self.num_kv_cache} KV caches, got {len(flat_kv_caches)}"
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
self.kv_cache_pointers.numpy()[:] = [t.data_ptr() for t in flat_kv_caches]
|
|
1467
|
+
assert device.type == "cuda", "The device should be CUDA."
|
|
1468
|
+
idx = device.index
|
|
1469
|
+
if idx not in self.kv_cache_pointers_on_gpu:
|
|
1470
|
+
self.kv_cache_pointers_on_gpu[idx] = torch.empty(
|
|
1471
|
+
self.num_kv_cache, dtype=torch.int64, device=device
|
|
1472
|
+
)
|
|
1473
|
+
self.kv_cache_pointers_on_gpu[idx].copy_(self.kv_cache_pointers)
|
|
1474
|
+
|
|
1475
|
+
# sglang MLA kv_caches[0].shape: [num_pages * page_size, 1, head_size]
|
|
1476
|
+
# sglang MHA kv_caches: [[k_list], [v_list]]
|
|
1477
|
+
# each with shape [num_pages * page_size, num_heads, head_size]
|
|
1478
|
+
self.page_buffer_size = get_page_buffer_size(kv_caches, self.gpu_kv_format)
|
|
1479
|
+
return self.kv_cache_pointers_on_gpu[idx]
|
|
1480
|
+
|
|
1481
|
+
@_lmcache_nvtx_annotate
|
|
1482
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1483
|
+
"""Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
|
|
1484
|
+
The kvcaches should correspond to the "WHOLE token sequence".
|
|
1485
|
+
|
|
1486
|
+
Note:
|
|
1487
|
+
1. This function expects the 'slot_mapping' is a "partial slot mapping"
|
|
1488
|
+
where its length is the same as the uncached token sequence.
|
|
1489
|
+
2. In the case that there is prefix caching, slot_mapping will starts
|
|
1490
|
+
with -1s until the end of the matched prefix. The start and end
|
|
1491
|
+
should NEVER overlap with the prefix caching (which means the
|
|
1492
|
+
underlying CUDA kernel will never see -1 in slot_mapping)
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
:raises ValueError: If 'kvcaches' is not provided in kwargs.
|
|
1496
|
+
:raises AssertionError: If the memory object does not have a tensor.
|
|
1497
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1498
|
+
"""
|
|
1499
|
+
assert memory_obj.tensor is not None
|
|
1500
|
+
|
|
1501
|
+
if self.use_mla:
|
|
1502
|
+
if memory_obj.metadata.fmt != MemoryFormat.KV_MLA_FMT:
|
|
1503
|
+
raise ValueError(
|
|
1504
|
+
"The memory object should be in KV_MLA_FMT format in"
|
|
1505
|
+
f" order to be processed by {self.__class__.__name__}"
|
|
1506
|
+
)
|
|
1507
|
+
else:
|
|
1508
|
+
if memory_obj.metadata.fmt != MemoryFormat.KV_2LTD:
|
|
1509
|
+
raise ValueError(
|
|
1510
|
+
"The memory object should be in KV_2LTD format in"
|
|
1511
|
+
f" order to be processed by {self.__class__.__name__}"
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
if "kvcaches" not in kwargs:
|
|
1515
|
+
raise ValueError("'kvcaches' should be provided in kwargs.")
|
|
1516
|
+
|
|
1517
|
+
if "slot_mapping" not in kwargs:
|
|
1518
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1519
|
+
|
|
1520
|
+
offset = kwargs.get("offset", 0)
|
|
1521
|
+
|
|
1522
|
+
kvcaches: List[torch.Tensor] = kwargs["kvcaches"]
|
|
1523
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1524
|
+
|
|
1525
|
+
kv_cache_pointers = self._initialize_pointers(kvcaches)
|
|
1526
|
+
lmc_ops.multi_layer_kv_transfer_unilateral(
|
|
1527
|
+
memory_obj.tensor,
|
|
1528
|
+
kv_cache_pointers,
|
|
1529
|
+
slot_mapping[start - offset : end - offset],
|
|
1530
|
+
kvcaches[0][0].device,
|
|
1531
|
+
self.page_buffer_size,
|
|
1532
|
+
lmc_ops.TransferDirection.H2D,
|
|
1533
|
+
self.gpu_kv_format,
|
|
1534
|
+
)
|
|
1535
|
+
|
|
1536
|
+
@_lmcache_nvtx_annotate
|
|
1537
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1538
|
+
"""Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
|
|
1539
|
+
The kvcaches should correspond to the "WHOLE token sequence".
|
|
1540
|
+
|
|
1541
|
+
Will set the memory_obj.metadata.fmt to MemoryFormat.KV_2LTD.
|
|
1542
|
+
|
|
1543
|
+
Note:
|
|
1544
|
+
1. This function expects the 'slot_mapping' is a "partial slot mapping"
|
|
1545
|
+
where its length is the same as the uncached token sequence.
|
|
1546
|
+
2. In the case that there is prefix caching, slot_mapping will starts
|
|
1547
|
+
with -1s until the end of the matched prefix. The start and end
|
|
1548
|
+
should NEVER overlap with the prefix caching (which means the
|
|
1549
|
+
underlying CUDA kernel will never see -1 in slot_mapping)
|
|
1550
|
+
|
|
1551
|
+
:raises ValueError: If 'kvcaches' is not provided in kwargs,
|
|
1552
|
+
:raises AssertionError: If the memory object does not have a tensor.
|
|
1553
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1554
|
+
"""
|
|
1555
|
+
assert memory_obj.tensor is not None
|
|
1556
|
+
|
|
1557
|
+
if "kvcaches" not in kwargs:
|
|
1558
|
+
raise ValueError("'kvcaches' should be provided in kwargs.")
|
|
1559
|
+
|
|
1560
|
+
if "slot_mapping" not in kwargs:
|
|
1561
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1562
|
+
|
|
1563
|
+
kvcaches: List[torch.Tensor] = kwargs["kvcaches"]
|
|
1564
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1565
|
+
|
|
1566
|
+
kv_cache_pointers = self._initialize_pointers(kvcaches)
|
|
1567
|
+
|
|
1568
|
+
if self.gpu_buffer is None or end - start != self.gpu_buffer.shape[2]:
|
|
1569
|
+
lmc_ops.multi_layer_kv_transfer_unilateral(
|
|
1570
|
+
memory_obj.tensor,
|
|
1571
|
+
kv_cache_pointers,
|
|
1572
|
+
slot_mapping[start:end],
|
|
1573
|
+
kvcaches[0][0].device,
|
|
1574
|
+
self.page_buffer_size,
|
|
1575
|
+
lmc_ops.TransferDirection.D2H,
|
|
1576
|
+
self.gpu_kv_format,
|
|
1577
|
+
)
|
|
1578
|
+
else:
|
|
1579
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
1580
|
+
assert self.gpu_buffer.device == kvcaches[0][0].device
|
|
1581
|
+
tmp_gpu_buffer = self.gpu_buffer[:, :, : end - start, :]
|
|
1582
|
+
lmc_ops.multi_layer_kv_transfer_unilateral(
|
|
1583
|
+
tmp_gpu_buffer,
|
|
1584
|
+
kv_cache_pointers,
|
|
1585
|
+
slot_mapping[start:end],
|
|
1586
|
+
kvcaches[0][0].device,
|
|
1587
|
+
self.page_buffer_size,
|
|
1588
|
+
lmc_ops.TransferDirection.D2H,
|
|
1589
|
+
self.gpu_kv_format,
|
|
1590
|
+
)
|
|
1591
|
+
memory_obj.tensor.copy_(tmp_gpu_buffer, non_blocking=True)
|
|
1592
|
+
|
|
1593
|
+
if not memory_obj.tensor.is_cuda:
|
|
1594
|
+
# Force a synchronize if the target buffer is NOT CUDA device
|
|
1595
|
+
# NOTE: for better performance, we may not want to sync for every
|
|
1596
|
+
# memory object
|
|
1597
|
+
torch.cuda.synchronize()
|
|
1598
|
+
|
|
1599
|
+
if self.use_mla:
|
|
1600
|
+
memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
|
|
1601
|
+
|
|
1602
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
1603
|
+
return torch.Size([2, self.num_layers, num_tokens, self.hidden_dim_size])
|
|
1604
|
+
|
|
1605
|
+
# TODO(Jiayi): need to optimize to enable real batching
|
|
1606
|
+
def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
1607
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
1608
|
+
self.to_gpu(memory_obj, start, end, **kwargs)
|
|
1609
|
+
|
|
1610
|
+
# TODO(Yuwei): need to optimize to enable real batching
|
|
1611
|
+
def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
|
|
1612
|
+
for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
|
|
1613
|
+
self.from_gpu(memory_obj, start, end, **kwargs)
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
# TODO: support MLA
|
|
1617
|
+
class SGLangLayerwiseGPUConnector(GPUConnectorInterface):
|
|
1618
|
+
"""
|
|
1619
|
+
The GPU KV cache should be a list of tensors, one for each layer,
|
|
1620
|
+
with separate key and value pointers.
|
|
1621
|
+
More specifically, we have:
|
|
1622
|
+
- kvcaches: Tuple[List[Tensor], List[Tensor]]
|
|
1623
|
+
- The first element is a list of key tensors, one per layer.
|
|
1624
|
+
- The second element is a list of value tensors, one per layer.
|
|
1625
|
+
- Each tensor: [page_buffer_size, head_num, head_size]
|
|
1626
|
+
|
|
1627
|
+
The connector manages the transfer of KV cache data between CPU and GPU
|
|
1628
|
+
memory for SGLang using pointer arrays for efficient access.
|
|
1629
|
+
It will produce/consume memory objects with KV_2LTD format.
|
|
1630
|
+
"""
|
|
1631
|
+
|
|
1632
|
+
def __init__(
|
|
1633
|
+
self, hidden_dim_size: int, num_layers: int, use_gpu: bool = False, **kwargs
|
|
1634
|
+
):
|
|
1635
|
+
assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
|
|
1636
|
+
self.dtype = kwargs["dtype"]
|
|
1637
|
+
assert "device" in kwargs, "device should be provided to create a GPU buffer."
|
|
1638
|
+
self.device = kwargs["device"]
|
|
1639
|
+
|
|
1640
|
+
self.hidden_dim_size = hidden_dim_size
|
|
1641
|
+
self.num_layers = num_layers
|
|
1642
|
+
|
|
1643
|
+
self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
|
|
1644
|
+
self.page_buffer_size = 0
|
|
1645
|
+
|
|
1646
|
+
self.gpu_buffer: Optional[torch.Tensor] = None
|
|
1647
|
+
self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
|
|
1648
|
+
|
|
1649
|
+
self.num_kv_cache = num_layers if self.use_mla else num_layers * 2
|
|
1650
|
+
self.element_size = torch.tensor([], dtype=self.dtype).element_size()
|
|
1651
|
+
self.kv_cache_pointers = torch.empty(
|
|
1652
|
+
self.num_kv_cache, dtype=torch.int64, device="cpu"
|
|
1653
|
+
)
|
|
1654
|
+
self.use_gpu = use_gpu
|
|
1655
|
+
self.gpu_buffer_allocator: Optional[GPUMemoryAllocator] = None
|
|
1656
|
+
|
|
1657
|
+
def _lazy_initialize_buffer(self, kv_caches):
|
|
1658
|
+
"""
|
|
1659
|
+
Lazily initialize the GPU buffer allocator if it is not initialized yet.
|
|
1660
|
+
Currently, we use the `kv_caches` (kv cache pointer) to determine
|
|
1661
|
+
the gpu buffer size in gpu connector.
|
|
1662
|
+
Also, the first request might be a bit slower due to buffer creation.
|
|
1663
|
+
"""
|
|
1664
|
+
if self.use_gpu and self.gpu_buffer_allocator is None:
|
|
1665
|
+
self.gpu_kv_format = discover_gpu_kv_format(kv_caches, EngineType.SGLANG)
|
|
1666
|
+
self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
|
|
1667
|
+
self.elements_per_layer = get_elements_per_layer(
|
|
1668
|
+
kv_caches, self.gpu_kv_format
|
|
1669
|
+
)
|
|
1670
|
+
logger.info(
|
|
1671
|
+
f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
|
|
1672
|
+
)
|
|
1673
|
+
gpu_buffer_size = self.elements_per_layer * self.element_size
|
|
1674
|
+
logger.info(
|
|
1675
|
+
f"Lazily initializing GPU buffer (gpu buffer size={gpu_buffer_size})."
|
|
1676
|
+
)
|
|
1677
|
+
self.gpu_buffer_allocator = GPUMemoryAllocator(
|
|
1678
|
+
gpu_buffer_size, device=self.device
|
|
1679
|
+
)
|
|
1680
|
+
|
|
1681
|
+
def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1682
|
+
raise NotImplementedError
|
|
1683
|
+
|
|
1684
|
+
def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
|
|
1685
|
+
raise NotImplementedError
|
|
1686
|
+
|
|
1687
|
+
@_lmcache_nvtx_annotate
|
|
1688
|
+
def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
|
|
1689
|
+
"""
|
|
1690
|
+
This function is a generator that moves the KV cache from the memory
|
|
1691
|
+
objects to paged GPU memory. The first iteration will prepare some
|
|
1692
|
+
related metadata. In each of the following iterations, it will first
|
|
1693
|
+
wait until the loading of the previous layer finish, and then load
|
|
1694
|
+
one layer of KV cache from the memory objects -> GPU buffer ->
|
|
1695
|
+
paged GPU memory. The last iteration simply waits for the last layer
|
|
1696
|
+
to finish.
|
|
1697
|
+
In total, this the generator will yield num_layers + 2 times.
|
|
1698
|
+
|
|
1699
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
1700
|
+
token sequence.
|
|
1701
|
+
|
|
1702
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
1703
|
+
token sequence.
|
|
1704
|
+
|
|
1705
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1706
|
+
"""
|
|
1707
|
+
|
|
1708
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
1709
|
+
assert self.kvcaches is not None, (
|
|
1710
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1713
|
+
if "slot_mapping" not in kwargs:
|
|
1714
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1715
|
+
|
|
1716
|
+
if "sync" not in kwargs:
|
|
1717
|
+
raise ValueError("'sync' should be provided in kwargs.")
|
|
1718
|
+
|
|
1719
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1720
|
+
|
|
1721
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
1722
|
+
|
|
1723
|
+
slot_mapping_chunks = []
|
|
1724
|
+
for start, end in zip(starts, ends, strict=False):
|
|
1725
|
+
slot_mapping_chunks.append(slot_mapping[start:end])
|
|
1726
|
+
|
|
1727
|
+
slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
|
|
1728
|
+
|
|
1729
|
+
num_tokens = len(slot_mapping_full)
|
|
1730
|
+
|
|
1731
|
+
if self.use_gpu:
|
|
1732
|
+
buffer_shape = self.get_shape(num_tokens)
|
|
1733
|
+
|
|
1734
|
+
assert self.gpu_buffer_allocator is not None, (
|
|
1735
|
+
"GPU buffer allocator should be initialized"
|
|
1736
|
+
)
|
|
1737
|
+
tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
1738
|
+
buffer_shape, self.dtype, MemoryFormat.KV_T2D
|
|
1739
|
+
)
|
|
1740
|
+
assert tmp_gpu_buffer_obj is not None, (
|
|
1741
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
1742
|
+
)
|
|
1743
|
+
assert tmp_gpu_buffer_obj.tensor is not None
|
|
1744
|
+
|
|
1745
|
+
offset = starts[0]
|
|
1746
|
+
|
|
1747
|
+
for layer_id in range(self.num_layers):
|
|
1748
|
+
memory_objs_layer = yield
|
|
1749
|
+
if layer_id > 0:
|
|
1750
|
+
logger.debug(f"Finished loading layer {layer_id - 1}")
|
|
1751
|
+
|
|
1752
|
+
# memobj -> gpu_buffer -> kvcaches
|
|
1753
|
+
for start, end, memory_obj in zip(
|
|
1754
|
+
starts, ends, memory_objs_layer, strict=False
|
|
1755
|
+
):
|
|
1756
|
+
assert memory_obj.metadata.fmt == MemoryFormat.KV_T2D
|
|
1757
|
+
if self.use_gpu:
|
|
1758
|
+
tmp_gpu_buffer_obj.tensor[start - offset : end - offset].copy_(
|
|
1759
|
+
memory_obj.tensor, non_blocking=True
|
|
1760
|
+
)
|
|
1761
|
+
else:
|
|
1762
|
+
lmc_ops.single_layer_kv_transfer_sgl(
|
|
1763
|
+
memory_obj.tensor,
|
|
1764
|
+
self.kvcaches[0][layer_id],
|
|
1765
|
+
self.kvcaches[1][layer_id],
|
|
1766
|
+
slot_mapping[start:end],
|
|
1767
|
+
lmc_ops.TransferDirection.H2D,
|
|
1768
|
+
token_major=True,
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
if self.use_gpu:
|
|
1772
|
+
t, h, d = self.kvcaches[0][layer_id].shape
|
|
1773
|
+
|
|
1774
|
+
lmc_ops.single_layer_kv_transfer_sgl(
|
|
1775
|
+
tmp_gpu_buffer_obj.tensor,
|
|
1776
|
+
self.kvcaches[0][layer_id].view(t, 1, h, d),
|
|
1777
|
+
self.kvcaches[1][layer_id].view(t, 1, h, d),
|
|
1778
|
+
slot_mapping_full,
|
|
1779
|
+
lmc_ops.TransferDirection.H2D,
|
|
1780
|
+
token_major=True,
|
|
1781
|
+
)
|
|
1782
|
+
|
|
1783
|
+
# free the buffer memory
|
|
1784
|
+
if self.use_gpu:
|
|
1785
|
+
tmp_gpu_buffer_obj.ref_count_down()
|
|
1786
|
+
|
|
1787
|
+
logger.debug(f"Finished loading layer {layer_id}")
|
|
1788
|
+
yield
|
|
1789
|
+
|
|
1790
|
+
@_lmcache_nvtx_annotate
|
|
1791
|
+
def batched_from_gpu(
|
|
1792
|
+
self,
|
|
1793
|
+
memory_objs: Union[List[List[MemoryObj]]],
|
|
1794
|
+
starts: List[int],
|
|
1795
|
+
ends: List[int],
|
|
1796
|
+
**kwargs,
|
|
1797
|
+
):
|
|
1798
|
+
"""
|
|
1799
|
+
This function is a generator that moves the KV cache from the paged GPU
|
|
1800
|
+
memory to the memory objects. The first iteration will prepare some
|
|
1801
|
+
related metadata and initiate the transfer in the first layer. In each
|
|
1802
|
+
of the following iterations, it will first wait until the storing of
|
|
1803
|
+
previous layer finishes, and then initiate string the KV cache of the
|
|
1804
|
+
current layer one. The storing process of the KV cache is paged GPU
|
|
1805
|
+
memory -> GPU buffer -> memory objects. The last iteration simply waits
|
|
1806
|
+
for the last layer to finish.
|
|
1807
|
+
In total, this the generator will yield num_layers + 1 times.
|
|
1808
|
+
|
|
1809
|
+
:param memory_objs: The memory objects to store the KV cache. The first
|
|
1810
|
+
dimension is the number of layers, and the second dimension is the
|
|
1811
|
+
number of memory objects (i.e., number of chunks) for each layer.
|
|
1812
|
+
|
|
1813
|
+
:param starts: The starting indices of the KV cache in the corresponding
|
|
1814
|
+
token sequence.
|
|
1815
|
+
|
|
1816
|
+
:param ends: The ending indices of the KV cache in the corresponding
|
|
1817
|
+
token sequence.
|
|
1818
|
+
|
|
1819
|
+
:raises ValueError: If 'slot_mapping' is not provided in kwargs.
|
|
1820
|
+
"""
|
|
1821
|
+
|
|
1822
|
+
self.initialize_kvcaches_ptr(**kwargs)
|
|
1823
|
+
assert self.kvcaches is not None, (
|
|
1824
|
+
"kvcaches should be provided in kwargs or initialized beforehand."
|
|
1825
|
+
)
|
|
1826
|
+
|
|
1827
|
+
if "slot_mapping" not in kwargs:
|
|
1828
|
+
raise ValueError("'slot_mapping' should be provided in kwargs.")
|
|
1829
|
+
|
|
1830
|
+
if "sync" not in kwargs:
|
|
1831
|
+
raise ValueError("'sync' should be provided in kwargs.")
|
|
1832
|
+
|
|
1833
|
+
slot_mapping: torch.Tensor = kwargs["slot_mapping"]
|
|
1834
|
+
|
|
1835
|
+
self._lazy_initialize_buffer(self.kvcaches)
|
|
1836
|
+
|
|
1837
|
+
slot_mapping_chunks = []
|
|
1838
|
+
for start, end in zip(starts, ends, strict=False):
|
|
1839
|
+
slot_mapping_chunks.append(slot_mapping[start:end])
|
|
1840
|
+
|
|
1841
|
+
slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
|
|
1842
|
+
|
|
1843
|
+
num_tokens = len(slot_mapping_full)
|
|
1844
|
+
|
|
1845
|
+
if self.use_gpu:
|
|
1846
|
+
buffer_shape = self.get_shape(num_tokens)
|
|
1847
|
+
|
|
1848
|
+
assert self.gpu_buffer_allocator is not None, (
|
|
1849
|
+
"GPU buffer allocator should be initialized"
|
|
1850
|
+
)
|
|
1851
|
+
tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
|
|
1852
|
+
buffer_shape, self.dtype, MemoryFormat.KV_T2D
|
|
1853
|
+
)
|
|
1854
|
+
assert tmp_gpu_buffer_obj is not None, (
|
|
1855
|
+
"Failed to allocate GPU buffer in GPUConnector"
|
|
1856
|
+
)
|
|
1857
|
+
assert tmp_gpu_buffer_obj.tensor is not None
|
|
1858
|
+
|
|
1859
|
+
for layer_id in range(self.num_layers):
|
|
1860
|
+
memory_objs_layer = memory_objs[layer_id]
|
|
1861
|
+
# kvcaches -> gpu_buffer -> memobj
|
|
1862
|
+
if self.use_gpu:
|
|
1863
|
+
t, h, d = self.kvcaches[0][layer_id].shape
|
|
1864
|
+
lmc_ops.single_layer_kv_transfer_sgl(
|
|
1865
|
+
tmp_gpu_buffer_obj.tensor,
|
|
1866
|
+
self.kvcaches[0][layer_id].view(t, 1, h, d),
|
|
1867
|
+
self.kvcaches[1][layer_id].view(t, 1, h, d),
|
|
1868
|
+
slot_mapping_full,
|
|
1869
|
+
lmc_ops.TransferDirection.D2H,
|
|
1870
|
+
token_major=True,
|
|
1871
|
+
)
|
|
1872
|
+
|
|
1873
|
+
start_idx = 0
|
|
1874
|
+
|
|
1875
|
+
for start, end, memory_obj in zip(
|
|
1876
|
+
starts, ends, memory_objs_layer, strict=False
|
|
1877
|
+
):
|
|
1878
|
+
assert memory_obj.tensor is not None
|
|
1879
|
+
if self.use_gpu:
|
|
1880
|
+
chunk_len = memory_obj.tensor.shape[0]
|
|
1881
|
+
memory_obj.tensor.copy_(
|
|
1882
|
+
tmp_gpu_buffer_obj.tensor[start_idx : start_idx + chunk_len],
|
|
1883
|
+
non_blocking=True,
|
|
1884
|
+
)
|
|
1885
|
+
start_idx += chunk_len
|
|
1886
|
+
else:
|
|
1887
|
+
lmc_ops.single_layer_kv_transfer_sgl(
|
|
1888
|
+
memory_obj.tensor,
|
|
1889
|
+
self.kvcaches[0][layer_id],
|
|
1890
|
+
self.kvcaches[1][layer_id],
|
|
1891
|
+
slot_mapping[start:end],
|
|
1892
|
+
lmc_ops.TransferDirection.D2H,
|
|
1893
|
+
token_major=True,
|
|
1894
|
+
)
|
|
1895
|
+
|
|
1896
|
+
yield
|
|
1897
|
+
logger.debug(f"Finished offloading layer {layer_id}")
|
|
1898
|
+
|
|
1899
|
+
# free the buffer memory
|
|
1900
|
+
if self.use_gpu:
|
|
1901
|
+
tmp_gpu_buffer_obj.ref_count_down()
|
|
1902
|
+
yield
|
|
1903
|
+
|
|
1904
|
+
def get_shape(self, num_tokens: int) -> torch.Size:
|
|
1905
|
+
# TODO: support MLA
|
|
1906
|
+
return torch.Size([num_tokens, 2, self.hidden_dim_size])
|