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,810 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from concurrent.futures import Future
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Sequence, Union
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
# Third Party
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
# First Party
|
|
12
|
+
from lmcache.integration.vllm.utils import get_size_bytes
|
|
13
|
+
from lmcache.logging import init_logger
|
|
14
|
+
from lmcache.observability import LMCStatsMonitor, PrometheusLogger
|
|
15
|
+
from lmcache.utils import CacheEngineKey, _lmcache_nvtx_annotate
|
|
16
|
+
from lmcache.v1.cache_controller.message import OpType
|
|
17
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
18
|
+
from lmcache.v1.memory_management import (
|
|
19
|
+
MemoryAllocatorInterface,
|
|
20
|
+
MemoryFormat,
|
|
21
|
+
MemoryObj,
|
|
22
|
+
MixedMemoryAllocator,
|
|
23
|
+
PagedCpuGpuMemoryAllocator,
|
|
24
|
+
)
|
|
25
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
26
|
+
from lmcache.v1.storage_backend.abstract_backend import AllocatorBackendInterface
|
|
27
|
+
from lmcache.v1.storage_backend.batched_message_sender import BatchedMessageSender
|
|
28
|
+
from lmcache.v1.storage_backend.cache_policy import get_cache_policy
|
|
29
|
+
from lmcache.v1.system_detection import NUMADetector, SystemMemoryDetector
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
# First Party
|
|
33
|
+
from lmcache.v1.cache_controller.worker import LMCacheWorker
|
|
34
|
+
|
|
35
|
+
logger = init_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LocalCPUBackend(AllocatorBackendInterface):
|
|
39
|
+
"""
|
|
40
|
+
Even if local_cpu is False (the hot_cache is not used), contains(),
|
|
41
|
+
insert_key(), remove(), get_blocking(), get_keys(), and clear()
|
|
42
|
+
are still callable by the storage manager.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
config: LMCacheEngineConfig,
|
|
48
|
+
metadata: Optional[LMCacheMetadata] = None,
|
|
49
|
+
dst_device: str = "cuda",
|
|
50
|
+
lmcache_worker: Optional["LMCacheWorker"] = None,
|
|
51
|
+
memory_allocator: Optional[MemoryAllocatorInterface] = None,
|
|
52
|
+
):
|
|
53
|
+
if torch.cuda.is_available():
|
|
54
|
+
super().__init__(dst_device)
|
|
55
|
+
else:
|
|
56
|
+
super().__init__("cpu")
|
|
57
|
+
|
|
58
|
+
self.cache_policy = get_cache_policy(config.cache_policy)
|
|
59
|
+
self.hot_cache = self.cache_policy.init_mutable_mapping()
|
|
60
|
+
|
|
61
|
+
self.use_hot = config.local_cpu
|
|
62
|
+
# NOTE: we keep the memory allocator argument for temporary
|
|
63
|
+
# test compatibility
|
|
64
|
+
# TODO: fix the tests to get rid the memory allocator
|
|
65
|
+
assert metadata is not None or memory_allocator is not None
|
|
66
|
+
self.memory_allocator = (
|
|
67
|
+
self.initialize_allocator(config, metadata) # type: ignore
|
|
68
|
+
if memory_allocator is None
|
|
69
|
+
else memory_allocator
|
|
70
|
+
)
|
|
71
|
+
self.lmcache_worker = lmcache_worker
|
|
72
|
+
self.instance_id = config.lmcache_instance_id
|
|
73
|
+
self.cpu_lock = threading.Lock()
|
|
74
|
+
|
|
75
|
+
self.stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
76
|
+
|
|
77
|
+
self.layerwise = config.use_layerwise
|
|
78
|
+
self.enable_blending = config.enable_blending
|
|
79
|
+
|
|
80
|
+
# Store config and metadata for chunk budget calculation
|
|
81
|
+
self.config = config
|
|
82
|
+
self.metadata = metadata
|
|
83
|
+
|
|
84
|
+
# to help maintain suffix -> prefix order in the dict
|
|
85
|
+
# assumption: only one request is looked up at a time
|
|
86
|
+
# (only one worker per cache engine)
|
|
87
|
+
self.keys_in_request: List[CacheEngineKey] = []
|
|
88
|
+
|
|
89
|
+
# Batched message sender for controller communication
|
|
90
|
+
self.batched_msg_sender: Optional[BatchedMessageSender] = None
|
|
91
|
+
|
|
92
|
+
# Initialize batched message sender
|
|
93
|
+
if lmcache_worker and metadata is not None:
|
|
94
|
+
self.batched_msg_sender = BatchedMessageSender(
|
|
95
|
+
metadata=metadata,
|
|
96
|
+
config=config,
|
|
97
|
+
location=str(self), # Backend location
|
|
98
|
+
lmcache_worker=lmcache_worker,
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
logger.warning("Controller message sender is not initialized")
|
|
102
|
+
|
|
103
|
+
self._setup_metrics()
|
|
104
|
+
|
|
105
|
+
def _setup_metrics(self):
|
|
106
|
+
prometheus_logger = PrometheusLogger.GetInstanceOrNone()
|
|
107
|
+
if prometheus_logger is not None:
|
|
108
|
+
prometheus_logger.local_cpu_hot_cache_count.set_function(
|
|
109
|
+
lambda: len(self.hot_cache)
|
|
110
|
+
)
|
|
111
|
+
prometheus_logger.local_cpu_keys_in_request_count.set_function(
|
|
112
|
+
lambda: len(self.keys_in_request)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def __str__(self):
|
|
116
|
+
return self.__class__.__name__
|
|
117
|
+
|
|
118
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
119
|
+
with self.cpu_lock:
|
|
120
|
+
if key not in self.hot_cache:
|
|
121
|
+
return False
|
|
122
|
+
if pin:
|
|
123
|
+
self.hot_cache[key].pin()
|
|
124
|
+
# vllm lookup sets pin to True
|
|
125
|
+
self.keys_in_request.append(key)
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def touch_cache(self):
|
|
129
|
+
# flip the order of the keys in the request
|
|
130
|
+
with self.cpu_lock:
|
|
131
|
+
for key in reversed(self.keys_in_request):
|
|
132
|
+
self.cache_policy.update_on_hit(key, self.hot_cache)
|
|
133
|
+
self.keys_in_request = []
|
|
134
|
+
|
|
135
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
contains() and exists_in_put_tasks() should be checked together
|
|
138
|
+
"""
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def submit_put_task(
|
|
142
|
+
self,
|
|
143
|
+
key: CacheEngineKey,
|
|
144
|
+
memory_obj: MemoryObj,
|
|
145
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
146
|
+
) -> Optional[Future]:
|
|
147
|
+
"""
|
|
148
|
+
Synchronously put the MemoryObj into the local cpu backend.
|
|
149
|
+
|
|
150
|
+
:param on_complete_callback: Optional callback invoked after the
|
|
151
|
+
synchronous put completes. Callback exceptions are caught and logged.
|
|
152
|
+
"""
|
|
153
|
+
stored = False
|
|
154
|
+
with self.cpu_lock:
|
|
155
|
+
if key in self.hot_cache:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
memory_obj.ref_count_up()
|
|
159
|
+
self.hot_cache[key] = memory_obj
|
|
160
|
+
|
|
161
|
+
self.cache_policy.update_on_put(key)
|
|
162
|
+
|
|
163
|
+
# Push kv admit msg with batching
|
|
164
|
+
if self.batched_msg_sender is not None:
|
|
165
|
+
self.batched_msg_sender.add_kv_op(
|
|
166
|
+
op_type=OpType.ADMIT,
|
|
167
|
+
key=key.chunk_hash,
|
|
168
|
+
)
|
|
169
|
+
stored = True
|
|
170
|
+
|
|
171
|
+
# Call callback after put completes (outside lock)
|
|
172
|
+
if stored and on_complete_callback is not None:
|
|
173
|
+
try:
|
|
174
|
+
on_complete_callback(key)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.warning(f"on_complete_callback failed for key {key}: {e}")
|
|
177
|
+
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def batched_submit_put_task(
|
|
181
|
+
self,
|
|
182
|
+
keys: Sequence[CacheEngineKey],
|
|
183
|
+
memory_objs: List[MemoryObj],
|
|
184
|
+
transfer_spec: Any = None,
|
|
185
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
186
|
+
) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Synchronously put the MemoryObjs into the local cpu backend.
|
|
189
|
+
|
|
190
|
+
:param on_complete_callback: Optional callback invoked once per key
|
|
191
|
+
after that key's put completes (not once per batch).
|
|
192
|
+
"""
|
|
193
|
+
if not self.use_hot:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# TODO(Jiayi): optimize this with batching
|
|
197
|
+
for key, memory_obj in zip(keys, memory_objs, strict=False):
|
|
198
|
+
self.submit_put_task(
|
|
199
|
+
key, memory_obj, on_complete_callback=on_complete_callback
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def get_blocking(
|
|
203
|
+
self,
|
|
204
|
+
key: CacheEngineKey,
|
|
205
|
+
) -> Optional[MemoryObj]:
|
|
206
|
+
with self.cpu_lock:
|
|
207
|
+
if key not in self.hot_cache:
|
|
208
|
+
return None
|
|
209
|
+
memory_obj = self.hot_cache[key]
|
|
210
|
+
# ref count up for caller to avoid situation where the memory_obj
|
|
211
|
+
# is evicted from the local cpu backend before the caller calls
|
|
212
|
+
# ref count up themselves
|
|
213
|
+
memory_obj.ref_count_up()
|
|
214
|
+
return memory_obj
|
|
215
|
+
|
|
216
|
+
async def batched_get_non_blocking(
|
|
217
|
+
self,
|
|
218
|
+
lookup_id: str,
|
|
219
|
+
keys: list[CacheEngineKey],
|
|
220
|
+
transfer_spec: Any = None,
|
|
221
|
+
) -> list[MemoryObj]:
|
|
222
|
+
mem_objs = []
|
|
223
|
+
with self.cpu_lock:
|
|
224
|
+
for key in keys:
|
|
225
|
+
mem_obj = self.hot_cache[key]
|
|
226
|
+
mem_obj.ref_count_up()
|
|
227
|
+
mem_objs.append(mem_obj)
|
|
228
|
+
return mem_objs
|
|
229
|
+
|
|
230
|
+
async def batched_async_contains(
|
|
231
|
+
self,
|
|
232
|
+
lookup_id: str,
|
|
233
|
+
keys: List[CacheEngineKey],
|
|
234
|
+
pin: bool = False,
|
|
235
|
+
) -> int:
|
|
236
|
+
# NOTE(Jiayi): Only prefix chunks are counted.
|
|
237
|
+
num_hit_chunks = 0
|
|
238
|
+
with self.cpu_lock:
|
|
239
|
+
for key in keys:
|
|
240
|
+
if key not in self.hot_cache:
|
|
241
|
+
return num_hit_chunks
|
|
242
|
+
if pin:
|
|
243
|
+
self.hot_cache[key].pin()
|
|
244
|
+
# vllm lookup sets pin to True
|
|
245
|
+
self.keys_in_request.append(key)
|
|
246
|
+
num_hit_chunks += 1
|
|
247
|
+
return num_hit_chunks
|
|
248
|
+
|
|
249
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
250
|
+
with self.cpu_lock:
|
|
251
|
+
if key not in self.hot_cache:
|
|
252
|
+
return False
|
|
253
|
+
memory_obj = self.hot_cache[key]
|
|
254
|
+
memory_obj.pin()
|
|
255
|
+
return True
|
|
256
|
+
|
|
257
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
258
|
+
with self.cpu_lock:
|
|
259
|
+
if key not in self.hot_cache:
|
|
260
|
+
return False
|
|
261
|
+
memory_obj = self.hot_cache[key]
|
|
262
|
+
memory_obj.unpin()
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
|
|
266
|
+
if force:
|
|
267
|
+
self.cpu_lock.acquire()
|
|
268
|
+
if key not in self.hot_cache:
|
|
269
|
+
if force:
|
|
270
|
+
self.cpu_lock.release()
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
memory_obj = self.hot_cache.pop(key)
|
|
274
|
+
memory_obj.ref_count_down()
|
|
275
|
+
|
|
276
|
+
if force:
|
|
277
|
+
self.cache_policy.update_on_force_evict(key)
|
|
278
|
+
self.cpu_lock.release()
|
|
279
|
+
|
|
280
|
+
if self.batched_msg_sender is not None:
|
|
281
|
+
self.batched_msg_sender.add_kv_op(
|
|
282
|
+
op_type=OpType.EVICT,
|
|
283
|
+
key=key.chunk_hash,
|
|
284
|
+
)
|
|
285
|
+
# NOTE (Jiayi): This `return True` might not accurately reflect
|
|
286
|
+
# whether the key is removed from the actual memory because
|
|
287
|
+
# other backends might still (temporarily) hold the memory object.
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
def _calculate_effective_cpu_size(
|
|
291
|
+
self,
|
|
292
|
+
configured_cpu_size: float,
|
|
293
|
+
config: LMCacheEngineConfig,
|
|
294
|
+
metadata: Optional[LMCacheMetadata] = None,
|
|
295
|
+
) -> float:
|
|
296
|
+
"""
|
|
297
|
+
Calculate the effective CPU memory size based on system available memory
|
|
298
|
+
and reserve memory configuration.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
configured_cpu_size: The configured CPU memory size in GB
|
|
302
|
+
config: The LMCache engine configuration
|
|
303
|
+
metadata: Optional metadata for first rank handling
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
The effective CPU memory size in GB
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
save_only_first_rank = (
|
|
310
|
+
metadata is not None
|
|
311
|
+
and config.get_extra_config_value("save_only_first_rank", metadata.use_mla)
|
|
312
|
+
and metadata.use_mla
|
|
313
|
+
)
|
|
314
|
+
if not save_only_first_rank:
|
|
315
|
+
# Do not adjust cpu_size if save_only_first_rank is False for now
|
|
316
|
+
return configured_cpu_size
|
|
317
|
+
|
|
318
|
+
# Get the system available memory and calculate effective cpu_size
|
|
319
|
+
system_available_memory_gb = SystemMemoryDetector.get_available_memory_gb()
|
|
320
|
+
# Get reserve memory size from config
|
|
321
|
+
reserve_cpu_size = config.reserve_local_cpu_size
|
|
322
|
+
|
|
323
|
+
# TODO(baoloongmao): For disable save_only_first_rank case,
|
|
324
|
+
# we need to avoid multi-rank race condition in future.
|
|
325
|
+
# But for enable save_only_first_rank case,
|
|
326
|
+
# we can handle reserve memory simply since non-first ranks
|
|
327
|
+
# do not allocate memory.
|
|
328
|
+
# Effective memory: min(configured_size, available_memory - reserve_size)
|
|
329
|
+
if system_available_memory_gb > 0:
|
|
330
|
+
max_usable_memory = max(0, system_available_memory_gb - reserve_cpu_size)
|
|
331
|
+
effective_cpu_size = min(configured_cpu_size, max_usable_memory)
|
|
332
|
+
logger.info(
|
|
333
|
+
f"Adjusted CPU memory size from {configured_cpu_size:.2f} GB "
|
|
334
|
+
f"to {effective_cpu_size:.2f} GB "
|
|
335
|
+
f"(system available: {system_available_memory_gb:.2f} GB, "
|
|
336
|
+
f"reserve: {reserve_cpu_size:.2f} GB)"
|
|
337
|
+
)
|
|
338
|
+
assert effective_cpu_size > 0
|
|
339
|
+
return effective_cpu_size
|
|
340
|
+
else:
|
|
341
|
+
logger.warning(
|
|
342
|
+
"Could not determine system available memory, using configured cpu_size"
|
|
343
|
+
)
|
|
344
|
+
return configured_cpu_size
|
|
345
|
+
|
|
346
|
+
def initialize_allocator(
|
|
347
|
+
self,
|
|
348
|
+
config: LMCacheEngineConfig,
|
|
349
|
+
metadata: Optional[LMCacheMetadata] = None,
|
|
350
|
+
) -> MemoryAllocatorInterface:
|
|
351
|
+
cpu_size = config.max_local_cpu_size
|
|
352
|
+
|
|
353
|
+
if metadata is not None:
|
|
354
|
+
# save_only_first_rank only works when use mla
|
|
355
|
+
save_only_first_rank = (
|
|
356
|
+
config.get_extra_config_value("save_only_first_rank", metadata.use_mla)
|
|
357
|
+
and metadata.use_mla
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if save_only_first_rank and metadata.is_first_rank():
|
|
361
|
+
# Only the first rank will save the cache,
|
|
362
|
+
# so we need to set it larger than other ranks
|
|
363
|
+
cpu_size = config.get_extra_config_value(
|
|
364
|
+
"first_rank_max_local_cpu_size", cpu_size
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Detect the numa mapping
|
|
368
|
+
numa_mapping = NUMADetector.get_numa_mapping(config)
|
|
369
|
+
logger.info(f"NUMA mapping {numa_mapping}")
|
|
370
|
+
|
|
371
|
+
# Calculate effective CPU memory size
|
|
372
|
+
cpu_size = self._calculate_effective_cpu_size(cpu_size, config, metadata)
|
|
373
|
+
cpu_size_bytes = int(cpu_size * 1024**3)
|
|
374
|
+
|
|
375
|
+
allocator_align_bytes = self._resolve_local_cpu_allocator_alignment(config)
|
|
376
|
+
if allocator_align_bytes is not None:
|
|
377
|
+
logger.info(
|
|
378
|
+
"LocalCPUBackend: using pinned allocation alignment=%d bytes",
|
|
379
|
+
allocator_align_bytes,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if config.enable_p2p:
|
|
383
|
+
# TODO(baoloongmao): Add lazy memory allocator support for P2P mode
|
|
384
|
+
# For now, keep the original P2P implementation
|
|
385
|
+
assert metadata is not None
|
|
386
|
+
shapes = metadata.get_shapes()
|
|
387
|
+
dtypes = metadata.get_dtypes()
|
|
388
|
+
|
|
389
|
+
paged_mem_allocator = PagedCpuGpuMemoryAllocator()
|
|
390
|
+
chunk_size_bytes = get_size_bytes(shapes, dtypes)
|
|
391
|
+
origin_cpu_size_bytes = cpu_size_bytes
|
|
392
|
+
align_cpu_size_bytes = (
|
|
393
|
+
origin_cpu_size_bytes // chunk_size_bytes * chunk_size_bytes
|
|
394
|
+
)
|
|
395
|
+
logger.info(
|
|
396
|
+
f"Auto align cpu size bytes, origin: {origin_cpu_size_bytes}, "
|
|
397
|
+
f"aligned: {align_cpu_size_bytes}, chunk size: {chunk_size_bytes}"
|
|
398
|
+
)
|
|
399
|
+
paged_mem_allocator.init_cpu_memory_allocator(
|
|
400
|
+
align_cpu_size_bytes,
|
|
401
|
+
shapes=shapes,
|
|
402
|
+
dtypes=dtypes,
|
|
403
|
+
fmt=MemoryFormat.KV_2LTD, # TODO: remove this hardcode
|
|
404
|
+
numa_mapping=numa_mapping,
|
|
405
|
+
)
|
|
406
|
+
return paged_mem_allocator
|
|
407
|
+
else:
|
|
408
|
+
# Check if lazy memory allocator should be enabled
|
|
409
|
+
use_lazy = (
|
|
410
|
+
config.enable_lazy_memory_allocator
|
|
411
|
+
and cpu_size > config.lazy_memory_safe_size
|
|
412
|
+
)
|
|
413
|
+
if use_lazy:
|
|
414
|
+
logger.warning(
|
|
415
|
+
"LazyMixedMemoryAllocator is temporarily unavailable; "
|
|
416
|
+
"falling back to MixedMemoryAllocator with full allocation. "
|
|
417
|
+
"Disable enable_lazy_memory_allocator or reduce "
|
|
418
|
+
"max_local_cpu_size to avoid large pinned allocations."
|
|
419
|
+
)
|
|
420
|
+
elif config.enable_lazy_memory_allocator:
|
|
421
|
+
logger.info(
|
|
422
|
+
f"LazyMixedMemoryAllocator is disabled because "
|
|
423
|
+
f"cpu_size ({cpu_size:.2f} GB) does not exceed "
|
|
424
|
+
f"lazy_memory_safe_size "
|
|
425
|
+
f"({config.lazy_memory_safe_size:.2f} GB). "
|
|
426
|
+
f"Using MixedMemoryAllocator instead."
|
|
427
|
+
)
|
|
428
|
+
if allocator_align_bytes is not None:
|
|
429
|
+
return MixedMemoryAllocator(
|
|
430
|
+
cpu_size_bytes,
|
|
431
|
+
numa_mapping=numa_mapping,
|
|
432
|
+
align_bytes=allocator_align_bytes,
|
|
433
|
+
)
|
|
434
|
+
return MixedMemoryAllocator(
|
|
435
|
+
cpu_size_bytes,
|
|
436
|
+
numa_mapping=numa_mapping,
|
|
437
|
+
config=config,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def _is_power_of_two(value: int) -> bool:
|
|
442
|
+
return value > 0 and (value & (value - 1)) == 0
|
|
443
|
+
|
|
444
|
+
def _resolve_local_cpu_allocator_alignment(
|
|
445
|
+
self, config: LMCacheEngineConfig
|
|
446
|
+
) -> Optional[int]:
|
|
447
|
+
"""
|
|
448
|
+
Determine pinned-memory alignment for LocalCPUBackend allocator.
|
|
449
|
+
|
|
450
|
+
Precedence:
|
|
451
|
+
1) explicit override: extra_config["local_cpu.pinned_align_bytes"]
|
|
452
|
+
2) rust raw block auto mode:
|
|
453
|
+
- rust_raw_block.device_path is set
|
|
454
|
+
- rust_raw_block.use_odirect is true
|
|
455
|
+
- rust_raw_block.align_local_cpu_allocator is true (default)
|
|
456
|
+
-> use rust_raw_block.block_align
|
|
457
|
+
3) None (use allocator default)
|
|
458
|
+
"""
|
|
459
|
+
extra = config.extra_config or {}
|
|
460
|
+
|
|
461
|
+
explicit_align = extra.get("local_cpu.pinned_align_bytes")
|
|
462
|
+
if explicit_align is not None:
|
|
463
|
+
align = int(explicit_align)
|
|
464
|
+
if not self._is_power_of_two(align):
|
|
465
|
+
raise ValueError(
|
|
466
|
+
"extra_config['local_cpu.pinned_align_bytes'] must be "
|
|
467
|
+
"a positive power of two"
|
|
468
|
+
)
|
|
469
|
+
return align
|
|
470
|
+
|
|
471
|
+
rust_device_path = extra.get("rust_raw_block.device_path")
|
|
472
|
+
rust_use_odirect = bool(extra.get("rust_raw_block.use_odirect", False))
|
|
473
|
+
rust_auto_align = bool(
|
|
474
|
+
extra.get("rust_raw_block.align_local_cpu_allocator", True)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if not rust_device_path or not rust_use_odirect or not rust_auto_align:
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
rust_block_align = int(extra.get("rust_raw_block.block_align", 4096))
|
|
481
|
+
if not self._is_power_of_two(rust_block_align):
|
|
482
|
+
raise ValueError(
|
|
483
|
+
"extra_config['rust_raw_block.block_align'] must be a positive "
|
|
484
|
+
"power of two when O_DIRECT alignment is enabled"
|
|
485
|
+
)
|
|
486
|
+
return rust_block_align
|
|
487
|
+
|
|
488
|
+
@_lmcache_nvtx_annotate
|
|
489
|
+
def allocate(
|
|
490
|
+
self,
|
|
491
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
492
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
493
|
+
fmt: Optional[MemoryFormat] = None,
|
|
494
|
+
eviction: bool = True,
|
|
495
|
+
busy_loop: bool = True,
|
|
496
|
+
) -> Optional[MemoryObj]:
|
|
497
|
+
"""
|
|
498
|
+
Allocate a memory object of shape and dtype
|
|
499
|
+
evict if necessary. Storage manager should always call
|
|
500
|
+
local_cpu_backend.allocate() to get memory objects
|
|
501
|
+
regardless of whether local_cpu is True or False
|
|
502
|
+
|
|
503
|
+
busy_loop should only be used for retrieve
|
|
504
|
+
the reasoning is that:
|
|
505
|
+
|
|
506
|
+
1. synchronous case
|
|
507
|
+
- many stores happen concurrently (if they busy_loop, deadlock happens)
|
|
508
|
+
- one retrieve at a time (okay to busy loop because stores will clear)
|
|
509
|
+
|
|
510
|
+
2. asynchronous case
|
|
511
|
+
- many stores happen concurrently (if they busy_loop, deadlock happens)
|
|
512
|
+
- many retrieves happen concurrently
|
|
513
|
+
(we use the async serializer to handle this)
|
|
514
|
+
"""
|
|
515
|
+
logger.debug(
|
|
516
|
+
f"Allocating memory in local cpu backend with busy loop: {busy_loop}"
|
|
517
|
+
)
|
|
518
|
+
if fmt is None:
|
|
519
|
+
if self.layerwise:
|
|
520
|
+
if self.enable_blending:
|
|
521
|
+
fmt = MemoryFormat.KV_2TD
|
|
522
|
+
else:
|
|
523
|
+
fmt = MemoryFormat.KV_T2D
|
|
524
|
+
else:
|
|
525
|
+
fmt = MemoryFormat.KV_2LTD
|
|
526
|
+
|
|
527
|
+
memory_obj = self.memory_allocator.allocate(shapes, dtypes, fmt)
|
|
528
|
+
if memory_obj is not None or not eviction:
|
|
529
|
+
return memory_obj
|
|
530
|
+
|
|
531
|
+
evict_keys_count = 0
|
|
532
|
+
num_attempts = 0
|
|
533
|
+
while True:
|
|
534
|
+
# whether or not this request needs to wait or other requests
|
|
535
|
+
wait_other_requests = True
|
|
536
|
+
if self.use_hot:
|
|
537
|
+
# TODO(Jiayi): optimize `num_candidates` with estimation.
|
|
538
|
+
# Accurate estimation is hard due to fragmentation
|
|
539
|
+
num_candidates = 1
|
|
540
|
+
evict_keys = None
|
|
541
|
+
with self.cpu_lock:
|
|
542
|
+
evict_keys = self.cache_policy.get_evict_candidates(
|
|
543
|
+
self.hot_cache, num_candidates=num_candidates
|
|
544
|
+
)
|
|
545
|
+
if evict_keys:
|
|
546
|
+
# we can continue trying to evict from the hot_cache
|
|
547
|
+
# and don't need to wait for other requests yet
|
|
548
|
+
wait_other_requests = False
|
|
549
|
+
logger.debug(
|
|
550
|
+
f"Evicting {len(evict_keys)} chunks from cpu memory"
|
|
551
|
+
)
|
|
552
|
+
# remove
|
|
553
|
+
self.batched_remove(evict_keys, force=False)
|
|
554
|
+
evict_keys_count += len(evict_keys)
|
|
555
|
+
else:
|
|
556
|
+
self.stats_monitor.update_local_cpu_evict_failed_count(
|
|
557
|
+
num_candidates
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
if wait_other_requests:
|
|
561
|
+
if not busy_loop:
|
|
562
|
+
logger.debug(
|
|
563
|
+
"Not busy looping because we are not immediately able to evict"
|
|
564
|
+
)
|
|
565
|
+
break
|
|
566
|
+
|
|
567
|
+
# TODO: make time_to_wait a config
|
|
568
|
+
time_to_wait = 0.1
|
|
569
|
+
logger.warning(
|
|
570
|
+
"No eviction candidates found in local cpu backend. "
|
|
571
|
+
"Local cpu memory is under pressure. "
|
|
572
|
+
f"Waiting for {time_to_wait} seconds before retrying."
|
|
573
|
+
)
|
|
574
|
+
# self.memory_allocator.memcheck()
|
|
575
|
+
# do not hold the lock during sleep
|
|
576
|
+
time.sleep(time_to_wait)
|
|
577
|
+
|
|
578
|
+
memory_obj = self.memory_allocator.allocate(shapes, dtypes, fmt)
|
|
579
|
+
if memory_obj is not None:
|
|
580
|
+
break
|
|
581
|
+
|
|
582
|
+
num_attempts += 1
|
|
583
|
+
logger.debug(
|
|
584
|
+
f"Unable to allocate memory object after {num_attempts}"
|
|
585
|
+
" attempts of local cpu backend allocate()"
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
self.stats_monitor.update_local_cpu_evict_metrics(evict_keys_count)
|
|
589
|
+
return memory_obj
|
|
590
|
+
|
|
591
|
+
@_lmcache_nvtx_annotate
|
|
592
|
+
def batched_allocate(
|
|
593
|
+
self,
|
|
594
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
595
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
596
|
+
batch_size: int,
|
|
597
|
+
fmt: Optional[MemoryFormat] = None,
|
|
598
|
+
eviction: bool = True,
|
|
599
|
+
busy_loop: bool = True,
|
|
600
|
+
) -> Optional[List[MemoryObj]]:
|
|
601
|
+
"""
|
|
602
|
+
Batched allocate `batch_size` memory objects of shape and dtype
|
|
603
|
+
evict if necessary. Storage manager should always call
|
|
604
|
+
local_cpu_backend.allocate() to get memory objects
|
|
605
|
+
regardless of whether local_cpu is True or False
|
|
606
|
+
|
|
607
|
+
busy_loop should only be used for retrieve
|
|
608
|
+
the reasoning is that:
|
|
609
|
+
|
|
610
|
+
1. synchronous case
|
|
611
|
+
- many stores happen concurrently (if they busy_loop, deadlock happens)
|
|
612
|
+
- one retrieve at a time (okay to busy loop because stores will clear)
|
|
613
|
+
|
|
614
|
+
2. asynchronous case
|
|
615
|
+
- many stores happen concurrently (if they busy_loop, deadlock happens)
|
|
616
|
+
- many retrieves happen concurrently
|
|
617
|
+
(we use the async serializer to handle this)
|
|
618
|
+
"""
|
|
619
|
+
logger.debug(
|
|
620
|
+
f"Batched allocating memory in local cpu backend"
|
|
621
|
+
f" with busy loop: {busy_loop}"
|
|
622
|
+
)
|
|
623
|
+
if fmt is None:
|
|
624
|
+
if self.layerwise:
|
|
625
|
+
if self.enable_blending:
|
|
626
|
+
fmt = MemoryFormat.KV_2TD
|
|
627
|
+
else:
|
|
628
|
+
fmt = MemoryFormat.KV_T2D
|
|
629
|
+
else:
|
|
630
|
+
fmt = MemoryFormat.KV_2LTD
|
|
631
|
+
|
|
632
|
+
memory_objs = self.memory_allocator.batched_allocate(
|
|
633
|
+
shapes, dtypes, batch_size, fmt
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
if memory_objs is not None or not eviction:
|
|
637
|
+
return memory_objs
|
|
638
|
+
|
|
639
|
+
assert isinstance(self.memory_allocator, MixedMemoryAllocator)
|
|
640
|
+
|
|
641
|
+
evict_keys_count = 0
|
|
642
|
+
num_attempts = 0
|
|
643
|
+
while True:
|
|
644
|
+
wait_other_requests = True
|
|
645
|
+
if self.use_hot:
|
|
646
|
+
# TODO(Jiayi): optimize `num_candidates` with estimation.
|
|
647
|
+
# Accurate estimation is hard due to fragmentation
|
|
648
|
+
num_candidates = 1
|
|
649
|
+
evict_keys = None
|
|
650
|
+
with self.cpu_lock:
|
|
651
|
+
evict_keys = self.cache_policy.get_evict_candidates(
|
|
652
|
+
self.hot_cache, num_candidates=num_candidates
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
# HACK: We assume batch_size=num_layers here.
|
|
656
|
+
# FIXME: We also assume if the one layer's ref_count > 1 or pinned,
|
|
657
|
+
# then the other layers are also ref_count > 1 or
|
|
658
|
+
# pinned in the cpu memory. This might not be true.
|
|
659
|
+
if evict_keys:
|
|
660
|
+
evict_keys_count += len(evict_keys)
|
|
661
|
+
wait_other_requests = False
|
|
662
|
+
for evict_key in evict_keys:
|
|
663
|
+
evict_key_all_layer = evict_key.split_layers(batch_size)
|
|
664
|
+
|
|
665
|
+
# TODO(Jiayi): batched allocate is not supported through
|
|
666
|
+
# `batched_remove`. Therefore, features like usage tracking
|
|
667
|
+
# is not supported.
|
|
668
|
+
old_mem_objs = []
|
|
669
|
+
for key in evict_key_all_layer:
|
|
670
|
+
old_mem_objs.append(self.hot_cache[key])
|
|
671
|
+
self.cache_policy.update_on_force_evict(key)
|
|
672
|
+
self.hot_cache.pop(key, None)
|
|
673
|
+
|
|
674
|
+
self.memory_allocator.batched_free(old_mem_objs)
|
|
675
|
+
|
|
676
|
+
logger.debug(
|
|
677
|
+
f"Evicting {len(old_mem_objs)} chunks from cpu memory"
|
|
678
|
+
)
|
|
679
|
+
else:
|
|
680
|
+
self.stats_monitor.update_local_cpu_evict_failed_count(
|
|
681
|
+
num_candidates
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if wait_other_requests:
|
|
685
|
+
if not busy_loop:
|
|
686
|
+
logger.debug(
|
|
687
|
+
"Not busy looping because we are not immediately able to evict"
|
|
688
|
+
)
|
|
689
|
+
break
|
|
690
|
+
|
|
691
|
+
# TODO: make time_to_wait a config
|
|
692
|
+
time_to_wait = 0.1
|
|
693
|
+
logger.warning(
|
|
694
|
+
"No eviction candidates found in local cpu backend. "
|
|
695
|
+
"Local cpu memory is under pressure. "
|
|
696
|
+
f"Waiting for {time_to_wait} seconds before retrying."
|
|
697
|
+
)
|
|
698
|
+
# self.memory_allocator.memcheck()
|
|
699
|
+
# do not hold the lock during sleep
|
|
700
|
+
time.sleep(time_to_wait)
|
|
701
|
+
|
|
702
|
+
memory_objs = self.memory_allocator.batched_allocate(
|
|
703
|
+
shapes, dtypes, batch_size, fmt
|
|
704
|
+
)
|
|
705
|
+
if memory_objs:
|
|
706
|
+
break
|
|
707
|
+
|
|
708
|
+
num_attempts += 1
|
|
709
|
+
logger.debug(
|
|
710
|
+
f"Unable to allocate memory object after {num_attempts}"
|
|
711
|
+
" attempts of local cpu backend batched_allocate()"
|
|
712
|
+
)
|
|
713
|
+
self.stats_monitor.update_local_cpu_evict_metrics(evict_keys_count)
|
|
714
|
+
return memory_objs
|
|
715
|
+
|
|
716
|
+
def get_full_chunk_size_bytes(self) -> int:
|
|
717
|
+
logger.info("Calculating the size of a single LMCache chunk")
|
|
718
|
+
assert self.metadata is not None, (
|
|
719
|
+
"metadata required for chunk budget calculation"
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
chunk_tokens = self.config.chunk_size
|
|
723
|
+
# already accounted for parallelism
|
|
724
|
+
kv_shape = (
|
|
725
|
+
self.metadata.kv_shape
|
|
726
|
+
) # [num_layers, kv_size, chunk_size, num_heads, head_size]
|
|
727
|
+
num_layers = kv_shape[0]
|
|
728
|
+
kv_size = kv_shape[1] # 1 for MLA, 2 for regular
|
|
729
|
+
# per gpu
|
|
730
|
+
num_heads = kv_shape[3]
|
|
731
|
+
head_size = kv_shape[4]
|
|
732
|
+
hidden_dim = num_heads * head_size
|
|
733
|
+
dtype_size = self.metadata.kv_dtype.itemsize
|
|
734
|
+
|
|
735
|
+
if self.layerwise:
|
|
736
|
+
# layerwise: [chunk_tokens, kv_size, hidden_dim]
|
|
737
|
+
chunk_bytes = chunk_tokens * kv_size * hidden_dim * dtype_size
|
|
738
|
+
else:
|
|
739
|
+
# full: [kv_size, num_layers, chunk_tokens, hidden_dim]
|
|
740
|
+
chunk_bytes = kv_size * num_layers * chunk_tokens * hidden_dim * dtype_size
|
|
741
|
+
logger.debug(
|
|
742
|
+
f"Stats received: num_layers={num_layers}, kv_size={kv_size}, "
|
|
743
|
+
f"chunk_tokens={chunk_tokens}, head_dim={head_size}, "
|
|
744
|
+
f"dtype_size={dtype_size}, "
|
|
745
|
+
f"hidden_dim={hidden_dim}"
|
|
746
|
+
)
|
|
747
|
+
logger.debug(f"Calculated bytes per chunk per rank: {chunk_bytes}")
|
|
748
|
+
return chunk_bytes
|
|
749
|
+
|
|
750
|
+
def calculate_chunk_budget(self) -> int:
|
|
751
|
+
"""
|
|
752
|
+
Calculate the maximum number of chunks that can be allocated concurrently
|
|
753
|
+
without causing memory deadlocks in the async loading system.
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
int: The estimated chunk budget for concurrent allocations
|
|
757
|
+
"""
|
|
758
|
+
total_memory = int(self.config.max_local_cpu_size * 1024**3)
|
|
759
|
+
chunk_bytes = self.get_full_chunk_size_bytes()
|
|
760
|
+
# add alignment overhead
|
|
761
|
+
# (MixedMemoryAllocator uses TensorMemoryAllocator with 4KB alignment)
|
|
762
|
+
assert hasattr(self.memory_allocator, "align_bytes")
|
|
763
|
+
alignment = self.memory_allocator.align_bytes
|
|
764
|
+
aligned_chunk_bytes = ((chunk_bytes + alignment - 1) // alignment) * alignment
|
|
765
|
+
|
|
766
|
+
# calculate budget with safety margin
|
|
767
|
+
max_chunks = total_memory // aligned_chunk_bytes
|
|
768
|
+
|
|
769
|
+
return max_chunks
|
|
770
|
+
|
|
771
|
+
def get_keys(self) -> List[CacheEngineKey]:
|
|
772
|
+
"""
|
|
773
|
+
array ordering of keys from LRU to MRU
|
|
774
|
+
"""
|
|
775
|
+
with self.cpu_lock:
|
|
776
|
+
return list(self.hot_cache.keys())
|
|
777
|
+
|
|
778
|
+
def clear(self) -> int:
|
|
779
|
+
"""
|
|
780
|
+
counts the number of memory objects removed
|
|
781
|
+
"""
|
|
782
|
+
if not self.use_hot:
|
|
783
|
+
return 0
|
|
784
|
+
clear_keys = []
|
|
785
|
+
num_cleared_tokens = 0
|
|
786
|
+
with self.cpu_lock:
|
|
787
|
+
for key in self.hot_cache:
|
|
788
|
+
memory_obj = self.hot_cache[key]
|
|
789
|
+
if not memory_obj.can_evict:
|
|
790
|
+
continue
|
|
791
|
+
clear_keys.append(key)
|
|
792
|
+
num_cleared_tokens += memory_obj.get_num_tokens()
|
|
793
|
+
|
|
794
|
+
# TODO(Jiayi): might not be accurate if we don't calculate
|
|
795
|
+
# `num_cleared_token` and remove the keys in an atomic way.
|
|
796
|
+
self.batched_remove(clear_keys)
|
|
797
|
+
|
|
798
|
+
return num_cleared_tokens
|
|
799
|
+
|
|
800
|
+
def get_allocator_backend(self):
|
|
801
|
+
return self
|
|
802
|
+
|
|
803
|
+
def get_memory_allocator(self):
|
|
804
|
+
return self.memory_allocator
|
|
805
|
+
|
|
806
|
+
def close(self) -> None:
|
|
807
|
+
if self.batched_msg_sender is not None:
|
|
808
|
+
self.batched_msg_sender.close()
|
|
809
|
+
self.memory_allocator.close()
|
|
810
|
+
self.clear()
|