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,656 @@
|
|
|
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
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
# Third Party
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
# First Party
|
|
14
|
+
from lmcache.logging import init_logger
|
|
15
|
+
from lmcache.observability import LMCStatsMonitor
|
|
16
|
+
from lmcache.utils import CacheEngineKey, DiskCacheMetadata, _lmcache_nvtx_annotate
|
|
17
|
+
from lmcache.v1.cache_controller.message import OpType
|
|
18
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
19
|
+
from lmcache.v1.memory_management import MemoryFormat, MemoryObj
|
|
20
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
21
|
+
from lmcache.v1.storage_backend.abstract_backend import StorageBackendInterface
|
|
22
|
+
from lmcache.v1.storage_backend.batched_message_sender import BatchedMessageSender
|
|
23
|
+
from lmcache.v1.storage_backend.cache_policy import get_cache_policy
|
|
24
|
+
from lmcache.v1.storage_backend.job_executor.pq_executor import (
|
|
25
|
+
AsyncPQThreadPoolExecutor,
|
|
26
|
+
)
|
|
27
|
+
from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
|
|
28
|
+
from lmcache.v1.storage_backend.path_sharder import PathSharder
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
# First Party
|
|
32
|
+
from lmcache.v1.cache_controller.worker import LMCacheWorker
|
|
33
|
+
|
|
34
|
+
logger = init_logger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# TODO(Jiayi): handle cases where cache is repetitvely prefetched.
|
|
38
|
+
class LocalDiskWorker:
|
|
39
|
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
40
|
+
self.put_lock = threading.Lock()
|
|
41
|
+
self.put_tasks: List[CacheEngineKey] = []
|
|
42
|
+
|
|
43
|
+
self.prefetch_lock = threading.Lock()
|
|
44
|
+
self.prefetch_tasks: dict[CacheEngineKey, Future] = {}
|
|
45
|
+
|
|
46
|
+
# TODO(Jiayi): make executor and its parameters configurable
|
|
47
|
+
self.executor = AsyncPQThreadPoolExecutor(loop, max_workers=4)
|
|
48
|
+
self.loop = loop
|
|
49
|
+
self._closed = False
|
|
50
|
+
|
|
51
|
+
async def submit_task(
|
|
52
|
+
self,
|
|
53
|
+
task_type: str,
|
|
54
|
+
task: Callable,
|
|
55
|
+
*args,
|
|
56
|
+
**kwargs,
|
|
57
|
+
) -> Any:
|
|
58
|
+
if task_type == "prefetch":
|
|
59
|
+
priority = 0
|
|
60
|
+
# self.insert_prefetch_task(kwargs["key"], None)
|
|
61
|
+
elif task_type == "delete":
|
|
62
|
+
priority = 1
|
|
63
|
+
elif task_type == "put":
|
|
64
|
+
priority = 2
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(f"Unknown task type: {task_type}")
|
|
67
|
+
|
|
68
|
+
return await self.executor.submit_job(
|
|
69
|
+
task,
|
|
70
|
+
*args,
|
|
71
|
+
priority=priority,
|
|
72
|
+
**kwargs,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def remove_put_task(self, key: CacheEngineKey):
|
|
76
|
+
with self.put_lock:
|
|
77
|
+
if key in self.put_tasks:
|
|
78
|
+
self.put_tasks.remove(key)
|
|
79
|
+
else:
|
|
80
|
+
logger.warning(f"Key {key} not found in put tasks.")
|
|
81
|
+
|
|
82
|
+
def insert_put_task(self, key: CacheEngineKey):
|
|
83
|
+
with self.put_lock:
|
|
84
|
+
self.put_tasks.append(key)
|
|
85
|
+
|
|
86
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
87
|
+
with self.put_lock:
|
|
88
|
+
return key in self.put_tasks
|
|
89
|
+
|
|
90
|
+
def close(self):
|
|
91
|
+
# Gracefully shut down the executor
|
|
92
|
+
if self._closed:
|
|
93
|
+
return
|
|
94
|
+
self._closed = True
|
|
95
|
+
self.executor.shutdown(wait=True)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class LocalDiskBackend(StorageBackendInterface):
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
config: LMCacheEngineConfig,
|
|
102
|
+
loop: asyncio.AbstractEventLoop,
|
|
103
|
+
local_cpu_backend: LocalCPUBackend,
|
|
104
|
+
dst_device: str = "cuda",
|
|
105
|
+
lmcache_worker: Optional["LMCacheWorker"] = None,
|
|
106
|
+
metadata: Optional[LMCacheMetadata] = None,
|
|
107
|
+
):
|
|
108
|
+
if torch.cuda.is_available():
|
|
109
|
+
super().__init__(dst_device)
|
|
110
|
+
else:
|
|
111
|
+
super().__init__("cpu")
|
|
112
|
+
|
|
113
|
+
self.cache_policy = get_cache_policy(config.cache_policy)
|
|
114
|
+
self.dict = self.cache_policy.init_mutable_mapping()
|
|
115
|
+
|
|
116
|
+
self.dst_device = dst_device
|
|
117
|
+
|
|
118
|
+
self.local_cpu_backend = local_cpu_backend
|
|
119
|
+
|
|
120
|
+
self.disk_lock = threading.Lock()
|
|
121
|
+
|
|
122
|
+
assert config.local_disk is not None
|
|
123
|
+
|
|
124
|
+
sharder = PathSharder(
|
|
125
|
+
raw_csv=config.local_disk,
|
|
126
|
+
strategy=config.local_disk_path_sharding,
|
|
127
|
+
dst_device=dst_device,
|
|
128
|
+
create_dirs=True,
|
|
129
|
+
)
|
|
130
|
+
self.path: str = sharder.selected
|
|
131
|
+
|
|
132
|
+
logger.info(
|
|
133
|
+
"Local disk cache path: %s (device %s, %d path(s) configured)",
|
|
134
|
+
self.path,
|
|
135
|
+
dst_device,
|
|
136
|
+
len(sharder.all_paths),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
self.loop = loop
|
|
140
|
+
|
|
141
|
+
self.use_local_cpu = config.local_cpu
|
|
142
|
+
|
|
143
|
+
# Block size (for file system I/O)
|
|
144
|
+
stat = os.statvfs(self.path)
|
|
145
|
+
self.os_disk_bs = stat.f_bsize
|
|
146
|
+
self.use_odirect = False
|
|
147
|
+
|
|
148
|
+
if config.extra_config is not None:
|
|
149
|
+
self.use_odirect = config.extra_config.get("use_odirect", False)
|
|
150
|
+
logger.info("Using O_DIRECT for disk I/O: %s", self.use_odirect)
|
|
151
|
+
|
|
152
|
+
self.disk_worker = LocalDiskWorker(loop)
|
|
153
|
+
|
|
154
|
+
# TODO(Jiayi): We need a disk space allocator to avoid fragmentation
|
|
155
|
+
# and hide the following details away from the backend.
|
|
156
|
+
self.max_cache_size = int(config.max_local_disk_size * 1024**3)
|
|
157
|
+
self.current_cache_size = 0.0
|
|
158
|
+
|
|
159
|
+
# to help maintain suffix -> prefix order in the dict
|
|
160
|
+
# assumption: only one request is looked up at a time
|
|
161
|
+
# (only one worker per cache engine)
|
|
162
|
+
self.keys_in_request: List[CacheEngineKey] = []
|
|
163
|
+
|
|
164
|
+
self.lmcache_worker = lmcache_worker
|
|
165
|
+
self.instance_id = config.lmcache_instance_id
|
|
166
|
+
self.stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
167
|
+
self.usage = 0
|
|
168
|
+
|
|
169
|
+
# Batched message sender for controller communication
|
|
170
|
+
self.batched_msg_sender: Optional[BatchedMessageSender] = None
|
|
171
|
+
|
|
172
|
+
# Initialize batched message sender
|
|
173
|
+
if lmcache_worker and metadata is not None:
|
|
174
|
+
self.batched_msg_sender = BatchedMessageSender(
|
|
175
|
+
metadata=metadata,
|
|
176
|
+
config=config,
|
|
177
|
+
location=str(self),
|
|
178
|
+
lmcache_worker=lmcache_worker,
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
logger.warning("Controller message sender is not initialized")
|
|
182
|
+
|
|
183
|
+
def __str__(self) -> str:
|
|
184
|
+
return "LocalDiskBackend"
|
|
185
|
+
|
|
186
|
+
def _key_to_path(
|
|
187
|
+
self,
|
|
188
|
+
key: CacheEngineKey,
|
|
189
|
+
) -> str:
|
|
190
|
+
return os.path.join(self.path, key.to_string().replace("/", "-") + ".pt")
|
|
191
|
+
|
|
192
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
193
|
+
with self.disk_lock:
|
|
194
|
+
if key not in self.dict:
|
|
195
|
+
return False
|
|
196
|
+
if pin:
|
|
197
|
+
self.dict[key].pin()
|
|
198
|
+
# vllm lookup sets pin to True
|
|
199
|
+
self.keys_in_request.append(key)
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
def touch_cache(self):
|
|
203
|
+
# flip the order of the keys in the request
|
|
204
|
+
with self.disk_lock:
|
|
205
|
+
for key in reversed(self.keys_in_request):
|
|
206
|
+
self.cache_policy.update_on_hit(key, self.dict)
|
|
207
|
+
self.keys_in_request = []
|
|
208
|
+
|
|
209
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
210
|
+
return self.disk_worker.exists_in_put_tasks(key)
|
|
211
|
+
|
|
212
|
+
def pin(
|
|
213
|
+
self,
|
|
214
|
+
key: CacheEngineKey,
|
|
215
|
+
) -> bool:
|
|
216
|
+
with self.disk_lock:
|
|
217
|
+
if key in self.dict:
|
|
218
|
+
self.dict[key].pin()
|
|
219
|
+
return True
|
|
220
|
+
else:
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
def unpin(
|
|
224
|
+
self,
|
|
225
|
+
key: CacheEngineKey,
|
|
226
|
+
) -> bool:
|
|
227
|
+
with self.disk_lock:
|
|
228
|
+
if key in self.dict:
|
|
229
|
+
self.dict[key].unpin()
|
|
230
|
+
return True
|
|
231
|
+
else:
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
def remove(
|
|
235
|
+
self,
|
|
236
|
+
key: CacheEngineKey,
|
|
237
|
+
force: bool = True,
|
|
238
|
+
) -> bool:
|
|
239
|
+
if force:
|
|
240
|
+
self.disk_lock.acquire()
|
|
241
|
+
|
|
242
|
+
if not (meta := self.dict.pop(key, None)):
|
|
243
|
+
if force:
|
|
244
|
+
self.disk_lock.release()
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
path = meta.path
|
|
248
|
+
size = meta.size
|
|
249
|
+
self.usage -= size
|
|
250
|
+
self.stats_monitor.update_local_storage_usage(self.usage)
|
|
251
|
+
|
|
252
|
+
# NOTE: The following code will cause deadlock
|
|
253
|
+
# res = asyncio.run_coroutine_threadsafe(
|
|
254
|
+
# self.disk_worker.submit_task("delete", os.remove, path),
|
|
255
|
+
# self.loop,
|
|
256
|
+
# )
|
|
257
|
+
# res.result()
|
|
258
|
+
|
|
259
|
+
os.remove(path)
|
|
260
|
+
|
|
261
|
+
if force:
|
|
262
|
+
self.cache_policy.update_on_force_evict(key)
|
|
263
|
+
self.disk_lock.release()
|
|
264
|
+
|
|
265
|
+
# Push kv evict msg with batching
|
|
266
|
+
if self.batched_msg_sender is not None:
|
|
267
|
+
self.batched_msg_sender.add_kv_op(
|
|
268
|
+
op_type=OpType.EVICT,
|
|
269
|
+
key=key.chunk_hash,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
def insert_key(
|
|
275
|
+
self,
|
|
276
|
+
key: CacheEngineKey,
|
|
277
|
+
size: int,
|
|
278
|
+
shape: torch.Size,
|
|
279
|
+
dtype: torch.dtype,
|
|
280
|
+
fmt: MemoryFormat,
|
|
281
|
+
cached_positions: Optional[torch.Tensor] = None,
|
|
282
|
+
) -> None:
|
|
283
|
+
path = self._key_to_path(key)
|
|
284
|
+
|
|
285
|
+
has_stored = False
|
|
286
|
+
with self.disk_lock:
|
|
287
|
+
if key in self.dict:
|
|
288
|
+
# Update cache recency
|
|
289
|
+
self.cache_policy.update_on_hit(key, self.dict)
|
|
290
|
+
has_stored = True
|
|
291
|
+
else:
|
|
292
|
+
self.dict[key] = DiskCacheMetadata(
|
|
293
|
+
path, size, shape, dtype, cached_positions, fmt, 0
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Push kv admit msg with batching
|
|
297
|
+
if self.batched_msg_sender is not None and not has_stored:
|
|
298
|
+
self.batched_msg_sender.add_kv_op(
|
|
299
|
+
op_type=OpType.ADMIT,
|
|
300
|
+
key=key.chunk_hash,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def submit_put_task(
|
|
304
|
+
self,
|
|
305
|
+
key: CacheEngineKey,
|
|
306
|
+
memory_obj: MemoryObj,
|
|
307
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
Submit a single put task to store KV cache to disk asynchronously.
|
|
311
|
+
|
|
312
|
+
:param key: The cache key for this KV chunk.
|
|
313
|
+
:param memory_obj: The memory object containing the KV data.
|
|
314
|
+
:param on_complete_callback: Optional callback invoked once per key
|
|
315
|
+
after the disk write completes. Callback exceptions are caught
|
|
316
|
+
and logged.
|
|
317
|
+
"""
|
|
318
|
+
assert memory_obj.tensor is not None
|
|
319
|
+
|
|
320
|
+
# skip repeated save
|
|
321
|
+
if self.exists_in_put_tasks(key):
|
|
322
|
+
logger.debug(f"Put task for {key} is already in progress.")
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
self.disk_worker.insert_put_task(key)
|
|
326
|
+
|
|
327
|
+
# TODO(Jiayi): Fragmentation is not considered here.
|
|
328
|
+
required_size = memory_obj.get_physical_size()
|
|
329
|
+
all_evict_keys = []
|
|
330
|
+
evict_success = True
|
|
331
|
+
with self.disk_lock:
|
|
332
|
+
while self.current_cache_size + required_size > self.max_cache_size:
|
|
333
|
+
evict_keys = self.cache_policy.get_evict_candidates(
|
|
334
|
+
self.dict, num_candidates=1
|
|
335
|
+
)
|
|
336
|
+
if not evict_keys:
|
|
337
|
+
logger.warning(
|
|
338
|
+
"No eviction candidates found. Disk space under pressure."
|
|
339
|
+
)
|
|
340
|
+
evict_success = False
|
|
341
|
+
break
|
|
342
|
+
|
|
343
|
+
for evict_key in evict_keys:
|
|
344
|
+
self.current_cache_size -= self.dict[evict_key].size
|
|
345
|
+
|
|
346
|
+
self.batched_remove(evict_keys, force=False)
|
|
347
|
+
|
|
348
|
+
all_evict_keys.extend(evict_keys)
|
|
349
|
+
if evict_success:
|
|
350
|
+
self.current_cache_size += required_size
|
|
351
|
+
self.cache_policy.update_on_put(key)
|
|
352
|
+
|
|
353
|
+
if not evict_success:
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
memory_obj.ref_count_up()
|
|
357
|
+
|
|
358
|
+
asyncio.run_coroutine_threadsafe(
|
|
359
|
+
self.disk_worker.submit_task(
|
|
360
|
+
"put",
|
|
361
|
+
self.async_save_bytes_to_disk,
|
|
362
|
+
key=key,
|
|
363
|
+
memory_obj=memory_obj,
|
|
364
|
+
on_complete_callback=on_complete_callback,
|
|
365
|
+
),
|
|
366
|
+
self.loop,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# TODO(Jiayi): enable real batching
|
|
370
|
+
def batched_submit_put_task(
|
|
371
|
+
self,
|
|
372
|
+
keys: Sequence[CacheEngineKey],
|
|
373
|
+
memory_objs: List[MemoryObj],
|
|
374
|
+
transfer_spec: Any = None,
|
|
375
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
376
|
+
) -> None:
|
|
377
|
+
"""
|
|
378
|
+
Submit batched put tasks to store KV caches to disk asynchronously.
|
|
379
|
+
|
|
380
|
+
:param keys: The cache keys for the KV chunks.
|
|
381
|
+
:param memory_objs: The memory objects containing the KV data.
|
|
382
|
+
:param transfer_spec: Optional transfer specification (unused).
|
|
383
|
+
:param on_complete_callback: Optional callback invoked once per key
|
|
384
|
+
after that key's disk write completes (not once per batch).
|
|
385
|
+
Callback exceptions are caught and logged.
|
|
386
|
+
"""
|
|
387
|
+
for key, memory_obj in zip(keys, memory_objs, strict=False):
|
|
388
|
+
self.submit_put_task(
|
|
389
|
+
key, memory_obj, on_complete_callback=on_complete_callback
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def get_blocking(
|
|
393
|
+
self,
|
|
394
|
+
key: CacheEngineKey,
|
|
395
|
+
) -> Optional[MemoryObj]:
|
|
396
|
+
"""
|
|
397
|
+
Blocking get function.
|
|
398
|
+
"""
|
|
399
|
+
self.disk_lock.acquire()
|
|
400
|
+
if key not in self.dict:
|
|
401
|
+
self.disk_lock.release()
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
# Update cache recency
|
|
405
|
+
self.cache_policy.update_on_hit(key, self.dict)
|
|
406
|
+
|
|
407
|
+
disk_meta = self.dict[key]
|
|
408
|
+
path = disk_meta.path
|
|
409
|
+
dtype = disk_meta.dtype
|
|
410
|
+
shape = disk_meta.shape
|
|
411
|
+
fmt = disk_meta.fmt
|
|
412
|
+
assert dtype is not None
|
|
413
|
+
assert shape is not None
|
|
414
|
+
|
|
415
|
+
self.disk_lock.release()
|
|
416
|
+
memory_obj = self.load_bytes_from_disk(
|
|
417
|
+
key, path, dtype=dtype, shape=shape, fmt=fmt
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return memory_obj
|
|
421
|
+
|
|
422
|
+
async def batched_get_non_blocking(
|
|
423
|
+
self,
|
|
424
|
+
lookup_id: str,
|
|
425
|
+
keys: list[CacheEngineKey],
|
|
426
|
+
transfer_spec: Any = None,
|
|
427
|
+
) -> list[MemoryObj]:
|
|
428
|
+
mem_objs: list[MemoryObj] = []
|
|
429
|
+
paths: list[str] = []
|
|
430
|
+
|
|
431
|
+
logger.debug(f"lookup_id: {lookup_id}; Prefetching {len(keys)} keys from disk.")
|
|
432
|
+
for key in keys:
|
|
433
|
+
self.disk_lock.acquire()
|
|
434
|
+
assert key in self.dict, f"Key {key} not found in disk cache after pinning"
|
|
435
|
+
|
|
436
|
+
path = self.dict[key].path
|
|
437
|
+
dtype = self.dict[key].dtype
|
|
438
|
+
shape = self.dict[key].shape
|
|
439
|
+
fmt = self.dict[key].fmt
|
|
440
|
+
|
|
441
|
+
assert dtype is not None
|
|
442
|
+
assert shape is not None
|
|
443
|
+
|
|
444
|
+
# busy_loop=False prevents spinning on the event loop thread;
|
|
445
|
+
# if staging memory is exhausted the caller will get a logged
|
|
446
|
+
# error rather than a silent deadlock.
|
|
447
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
448
|
+
shape,
|
|
449
|
+
dtype,
|
|
450
|
+
fmt,
|
|
451
|
+
busy_loop=False,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if memory_obj is None:
|
|
455
|
+
logger.error(
|
|
456
|
+
"Memory allocation failed during async disk load for key %s. "
|
|
457
|
+
"CPU staging pool may be exhausted (unpin() not called after "
|
|
458
|
+
"a previous retrieve). Returning partial results.",
|
|
459
|
+
key,
|
|
460
|
+
)
|
|
461
|
+
return mem_objs
|
|
462
|
+
|
|
463
|
+
self.dict[key].pin()
|
|
464
|
+
|
|
465
|
+
# NOTE(Jiayi): Currently, we consider prefetch as cache hit.
|
|
466
|
+
# Update cache recency
|
|
467
|
+
self.cache_policy.update_on_hit(key, self.dict)
|
|
468
|
+
|
|
469
|
+
self.disk_lock.release()
|
|
470
|
+
logger.debug(f"Prefetching {key} from disk.")
|
|
471
|
+
memory_obj.pin()
|
|
472
|
+
mem_objs.append(memory_obj)
|
|
473
|
+
paths.append(path)
|
|
474
|
+
|
|
475
|
+
return await self.disk_worker.submit_task(
|
|
476
|
+
"prefetch",
|
|
477
|
+
self.batched_async_load_bytes_from_disk,
|
|
478
|
+
paths=paths,
|
|
479
|
+
keys=keys,
|
|
480
|
+
memory_objs=mem_objs,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
async def batched_async_contains(
|
|
484
|
+
self,
|
|
485
|
+
lookup_id: str,
|
|
486
|
+
keys: list[CacheEngineKey],
|
|
487
|
+
pin: bool = False,
|
|
488
|
+
) -> int:
|
|
489
|
+
num_hit_counts = 0
|
|
490
|
+
with self.disk_lock:
|
|
491
|
+
for key in keys:
|
|
492
|
+
if key not in self.dict:
|
|
493
|
+
return num_hit_counts
|
|
494
|
+
if pin:
|
|
495
|
+
self.dict[key].pin()
|
|
496
|
+
self.keys_in_request.append(key)
|
|
497
|
+
num_hit_counts += 1
|
|
498
|
+
return num_hit_counts
|
|
499
|
+
|
|
500
|
+
@_lmcache_nvtx_annotate
|
|
501
|
+
@torch.inference_mode()
|
|
502
|
+
def async_save_bytes_to_disk(
|
|
503
|
+
self,
|
|
504
|
+
key: CacheEngineKey,
|
|
505
|
+
memory_obj: MemoryObj,
|
|
506
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
507
|
+
) -> None:
|
|
508
|
+
"""
|
|
509
|
+
Convert KV to bytes and async store bytes to disk.
|
|
510
|
+
|
|
511
|
+
:param on_complete_callback: Optional callback invoked after the disk
|
|
512
|
+
write completes for this key. Callback exceptions are caught and
|
|
513
|
+
logged.
|
|
514
|
+
"""
|
|
515
|
+
kv_chunk = memory_obj.tensor
|
|
516
|
+
assert kv_chunk is not None
|
|
517
|
+
buffer = memory_obj.byte_array
|
|
518
|
+
path = self._key_to_path(key)
|
|
519
|
+
|
|
520
|
+
size = len(buffer)
|
|
521
|
+
self.usage += size
|
|
522
|
+
self.stats_monitor.update_local_storage_usage(self.usage)
|
|
523
|
+
|
|
524
|
+
# TODO(Jiayi): need to add ref count in disk memory object
|
|
525
|
+
self.write_file(buffer, path)
|
|
526
|
+
|
|
527
|
+
# ref count down here because there's a ref_count_up in
|
|
528
|
+
# `submit_put_task` above.
|
|
529
|
+
# Ref count down better be before `insert_key` for testing
|
|
530
|
+
# purposes (e.g., testing mem_leak).
|
|
531
|
+
# TODO(Jiayi): This could be problematic if the
|
|
532
|
+
# freed memory object is immediately reused.
|
|
533
|
+
size = memory_obj.get_physical_size()
|
|
534
|
+
shape = memory_obj.metadata.shape
|
|
535
|
+
dtype = memory_obj.metadata.dtype
|
|
536
|
+
fmt = memory_obj.metadata.fmt
|
|
537
|
+
cached_positions = memory_obj.metadata.cached_positions
|
|
538
|
+
memory_obj.ref_count_down()
|
|
539
|
+
|
|
540
|
+
self.insert_key(key, size, shape, dtype, fmt, cached_positions=cached_positions)
|
|
541
|
+
|
|
542
|
+
self.disk_worker.remove_put_task(key)
|
|
543
|
+
|
|
544
|
+
# Call the completion callback if provided
|
|
545
|
+
if on_complete_callback is not None:
|
|
546
|
+
try:
|
|
547
|
+
on_complete_callback(key)
|
|
548
|
+
except Exception as e:
|
|
549
|
+
logger.warning(f"on_complete_callback failed for key {key}: {e}")
|
|
550
|
+
|
|
551
|
+
def batched_async_load_bytes_from_disk(
|
|
552
|
+
self,
|
|
553
|
+
paths: list[str],
|
|
554
|
+
keys: list[CacheEngineKey],
|
|
555
|
+
memory_objs: list[MemoryObj],
|
|
556
|
+
write_back: bool = False,
|
|
557
|
+
) -> list[MemoryObj]:
|
|
558
|
+
"""
|
|
559
|
+
Async load bytearray from disk.
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
logger.debug("Executing `async_load_bytes` from disk.")
|
|
563
|
+
# TODO (Jiayi): handle the case where loading fails.
|
|
564
|
+
for path, key, mem_obj in zip(paths, keys, memory_objs, strict=False):
|
|
565
|
+
buffer = mem_obj.byte_array
|
|
566
|
+
self.read_file(key, buffer, path)
|
|
567
|
+
|
|
568
|
+
# TODO(Jiayi): Please recover the metadata in a more
|
|
569
|
+
# elegant way in the future.
|
|
570
|
+
cached_positions = self.dict[key].cached_positions
|
|
571
|
+
mem_obj.metadata.cached_positions = cached_positions
|
|
572
|
+
|
|
573
|
+
self.disk_lock.acquire()
|
|
574
|
+
self.dict[key].unpin()
|
|
575
|
+
self.disk_lock.release()
|
|
576
|
+
|
|
577
|
+
return memory_objs
|
|
578
|
+
|
|
579
|
+
def load_bytes_from_disk(
|
|
580
|
+
self,
|
|
581
|
+
key: CacheEngineKey,
|
|
582
|
+
path: str,
|
|
583
|
+
dtype: torch.dtype,
|
|
584
|
+
shape: torch.Size,
|
|
585
|
+
fmt: MemoryFormat,
|
|
586
|
+
) -> Optional[MemoryObj]:
|
|
587
|
+
"""
|
|
588
|
+
Load bytearray from disk.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
memory_obj = self.local_cpu_backend.allocate(shape, dtype, fmt)
|
|
592
|
+
assert memory_obj is not None, "Memory allocation failed during disk load."
|
|
593
|
+
|
|
594
|
+
buffer = memory_obj.byte_array
|
|
595
|
+
self.read_file(key, buffer, path)
|
|
596
|
+
|
|
597
|
+
# TODO(Jiayi): Please recover the metadata in a more
|
|
598
|
+
# elegant way in the future.
|
|
599
|
+
cached_positions = self.dict[key].cached_positions
|
|
600
|
+
memory_obj.metadata.cached_positions = cached_positions
|
|
601
|
+
|
|
602
|
+
return memory_obj
|
|
603
|
+
|
|
604
|
+
def write_file(self, buffer, path):
|
|
605
|
+
start_time = time.time()
|
|
606
|
+
size = len(buffer)
|
|
607
|
+
if size % self.os_disk_bs != 0 or not self.use_odirect:
|
|
608
|
+
with open(path, "wb") as f:
|
|
609
|
+
f.write(buffer)
|
|
610
|
+
else:
|
|
611
|
+
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_DIRECT, 0o644)
|
|
612
|
+
os.write(fd, buffer)
|
|
613
|
+
os.close(fd)
|
|
614
|
+
disk_write_time = time.time() - start_time
|
|
615
|
+
logger.debug(
|
|
616
|
+
f"Disk write size: {size} bytes, "
|
|
617
|
+
f"Bandwidth: {size / disk_write_time / 1e6:.2f} MB/s"
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def read_file(self, key, buffer, path):
|
|
621
|
+
start_time = time.time()
|
|
622
|
+
size = len(buffer)
|
|
623
|
+
fblock_aligned = size % self.os_disk_bs == 0
|
|
624
|
+
if not fblock_aligned and self.use_odirect:
|
|
625
|
+
logger.warning(
|
|
626
|
+
"Cannot use O_DIRECT for this file, "
|
|
627
|
+
"size is not aligned to disk block size."
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
try:
|
|
631
|
+
if not fblock_aligned or not self.use_odirect:
|
|
632
|
+
with open(path, "rb") as f:
|
|
633
|
+
f.readinto(buffer)
|
|
634
|
+
else:
|
|
635
|
+
fd = os.open(path, os.O_RDONLY | os.O_DIRECT)
|
|
636
|
+
with os.fdopen(fd, "rb", buffering=0) as fdo:
|
|
637
|
+
fdo.readinto(buffer)
|
|
638
|
+
except FileNotFoundError:
|
|
639
|
+
logger.warning(f"File not found on disk: {path}")
|
|
640
|
+
if self.dict.get(key, None):
|
|
641
|
+
self.dict.pop(key)
|
|
642
|
+
return
|
|
643
|
+
|
|
644
|
+
disk_read_time = time.time() - start_time
|
|
645
|
+
logger.debug(
|
|
646
|
+
f"Disk read size: {size} bytes, "
|
|
647
|
+
f"Bandwidth: {size / disk_read_time / 1e6:.2f} MB/s"
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
def get_allocator_backend(self) -> LocalCPUBackend:
|
|
651
|
+
return self.local_cpu_backend
|
|
652
|
+
|
|
653
|
+
def close(self) -> None:
|
|
654
|
+
if self.batched_msg_sender is not None:
|
|
655
|
+
self.batched_msg_sender.close()
|
|
656
|
+
self.disk_worker.close()
|