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,1400 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2024-2025 LMCache Authors.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
# Standard
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any, Callable, List, Optional, Sequence, Set, Union, cast
|
|
20
|
+
from urllib.parse import quote as url_quote
|
|
21
|
+
import asyncio
|
|
22
|
+
import os
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
import uuid
|
|
26
|
+
|
|
27
|
+
# Third Party
|
|
28
|
+
from nixl._api import nixl_agent as NixlAgent
|
|
29
|
+
from nixl._api import nixl_agent_config as NixlAgentConfig
|
|
30
|
+
from nixl._api import nixl_prepped_dlist_handle as NixlDlistHandle
|
|
31
|
+
from nixl._api import nixl_xfer_handle as NixlXferHandle
|
|
32
|
+
from nixl._api import (
|
|
33
|
+
nixlBind,
|
|
34
|
+
)
|
|
35
|
+
import torch
|
|
36
|
+
|
|
37
|
+
# First Party
|
|
38
|
+
from lmcache.logging import init_logger
|
|
39
|
+
from lmcache.utils import CacheEngineKey
|
|
40
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
41
|
+
from lmcache.v1.memory_management import (
|
|
42
|
+
MemoryFormat,
|
|
43
|
+
MemoryObj,
|
|
44
|
+
MemoryObjMetadata,
|
|
45
|
+
PagedTensorMemoryAllocator,
|
|
46
|
+
_allocate_cpu_memory,
|
|
47
|
+
_allocate_gpu_memory,
|
|
48
|
+
_free_cpu_memory,
|
|
49
|
+
)
|
|
50
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
51
|
+
from lmcache.v1.storage_backend.abstract_backend import AllocatorBackendInterface
|
|
52
|
+
from lmcache.v1.storage_backend.cache_policy import get_cache_policy
|
|
53
|
+
from lmcache.v1.transfer_channel.transfer_utils import get_correct_device
|
|
54
|
+
|
|
55
|
+
logger = init_logger(__name__)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class NixlStorageConfig:
|
|
60
|
+
buffer_size: int
|
|
61
|
+
pool_size: int
|
|
62
|
+
buffer_device: str
|
|
63
|
+
backend: str
|
|
64
|
+
backend_params: dict[str, str]
|
|
65
|
+
dynamic_storage: bool
|
|
66
|
+
enable_presence_cache: bool
|
|
67
|
+
enable_async_put: bool
|
|
68
|
+
use_direct_io: bool
|
|
69
|
+
path: str
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def validate_nixl_backend(dynamic_storage: bool, backend: str, device: str):
|
|
73
|
+
if dynamic_storage: # For now only supports OBJ backend
|
|
74
|
+
if backend in ("OBJ",):
|
|
75
|
+
return device == "cpu" or device == "cuda"
|
|
76
|
+
else:
|
|
77
|
+
return False
|
|
78
|
+
else:
|
|
79
|
+
if backend in ("GDS", "GDS_MT", "OBJ"):
|
|
80
|
+
return device == "cpu" or device == "cuda"
|
|
81
|
+
elif backend in ("POSIX", "HF3FS"):
|
|
82
|
+
return device == "cpu"
|
|
83
|
+
else:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def from_cache_engine_config(
|
|
88
|
+
config: LMCacheEngineConfig, metadata: LMCacheMetadata
|
|
89
|
+
):
|
|
90
|
+
assert config.nixl_buffer_size is not None
|
|
91
|
+
assert config.nixl_buffer_device is not None
|
|
92
|
+
|
|
93
|
+
extra_config = config.extra_config
|
|
94
|
+
assert extra_config is not None
|
|
95
|
+
assert extra_config.get("enable_nixl_storage")
|
|
96
|
+
|
|
97
|
+
enable_presence_cache = extra_config.get("nixl_presence_cache", False)
|
|
98
|
+
enable_async_put = extra_config.get("nixl_async_put", False)
|
|
99
|
+
backend_params = extra_config.get("nixl_backend_params", {})
|
|
100
|
+
use_direct_io = extra_config.get("use_direct_io", False)
|
|
101
|
+
pool_size = extra_config.get("nixl_pool_size")
|
|
102
|
+
backend = extra_config.get("nixl_backend")
|
|
103
|
+
path = extra_config.get("nixl_path")
|
|
104
|
+
|
|
105
|
+
assert pool_size is not None
|
|
106
|
+
assert backend is not None
|
|
107
|
+
assert use_direct_io in [False, True]
|
|
108
|
+
|
|
109
|
+
dynamic_storage = pool_size == 0
|
|
110
|
+
if dynamic_storage:
|
|
111
|
+
assert not config.save_unfull_chunk, (
|
|
112
|
+
"save_unfull_chunk should be set to False when using dynamic storage"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
corrected_device = get_correct_device(
|
|
116
|
+
config.nixl_buffer_device, metadata.worker_id
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
assert NixlStorageConfig.validate_nixl_backend(
|
|
120
|
+
dynamic_storage, backend, config.nixl_buffer_device
|
|
121
|
+
), "Invalid NIXL backend & device combination"
|
|
122
|
+
|
|
123
|
+
return NixlStorageConfig(
|
|
124
|
+
buffer_size=config.nixl_buffer_size,
|
|
125
|
+
pool_size=pool_size,
|
|
126
|
+
buffer_device=corrected_device,
|
|
127
|
+
backend=backend,
|
|
128
|
+
backend_params=backend_params,
|
|
129
|
+
dynamic_storage=dynamic_storage,
|
|
130
|
+
enable_presence_cache=enable_presence_cache,
|
|
131
|
+
enable_async_put=enable_async_put,
|
|
132
|
+
use_direct_io=use_direct_io,
|
|
133
|
+
path=path,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class NixlDescPool(ABC):
|
|
138
|
+
def __init__(self, size: int):
|
|
139
|
+
self.lock = threading.Lock()
|
|
140
|
+
self.size: int = size
|
|
141
|
+
self.indices: List[int] = []
|
|
142
|
+
self.indices.extend(reversed(range(size)))
|
|
143
|
+
|
|
144
|
+
def get_num_available_descs(self) -> int:
|
|
145
|
+
with self.lock:
|
|
146
|
+
return len(self.indices)
|
|
147
|
+
|
|
148
|
+
def pop(self) -> int:
|
|
149
|
+
with self.lock:
|
|
150
|
+
assert len(self.indices) > 0
|
|
151
|
+
return self.indices.pop()
|
|
152
|
+
|
|
153
|
+
def push(self, index: int):
|
|
154
|
+
with self.lock:
|
|
155
|
+
assert len(self.indices) < self.size
|
|
156
|
+
self.indices.append(index)
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def close(self):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class NixlFilePool(NixlDescPool):
|
|
164
|
+
def __init__(self, size: int, path: str, use_direct_io: bool):
|
|
165
|
+
super().__init__(size)
|
|
166
|
+
self.fds: List[int] = []
|
|
167
|
+
|
|
168
|
+
assert path is not None
|
|
169
|
+
|
|
170
|
+
flags = os.O_CREAT | os.O_RDWR
|
|
171
|
+
if use_direct_io:
|
|
172
|
+
if hasattr(os, "O_DIRECT"):
|
|
173
|
+
flags |= os.O_DIRECT
|
|
174
|
+
else:
|
|
175
|
+
logger.warning(
|
|
176
|
+
"use_direct_io is True, but O_DIRECT is not available on "
|
|
177
|
+
"this system. Falling back to buffered I/O."
|
|
178
|
+
)
|
|
179
|
+
for i in reversed(range(size)):
|
|
180
|
+
filename = f"obj_{i}_{uuid.uuid4().hex[0:4]}.bin"
|
|
181
|
+
tmp_path = os.path.join(path, filename)
|
|
182
|
+
fd = os.open(tmp_path, flags)
|
|
183
|
+
self.fds.append(fd)
|
|
184
|
+
|
|
185
|
+
def close(self):
|
|
186
|
+
# TODO: do we need to delete the files?
|
|
187
|
+
with self.lock:
|
|
188
|
+
assert len(self.fds) == self.size
|
|
189
|
+
for fd in self.fds:
|
|
190
|
+
os.close(fd)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class NixlObjectPool(NixlDescPool):
|
|
194
|
+
def __init__(self, size: int):
|
|
195
|
+
super().__init__(size)
|
|
196
|
+
self.keys: List[str] = []
|
|
197
|
+
|
|
198
|
+
for i in reversed(range(size)):
|
|
199
|
+
key = f"obj_{i}_{uuid.uuid4().hex[0:4]}"
|
|
200
|
+
self.keys.append(key)
|
|
201
|
+
|
|
202
|
+
def close(self):
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class SetPresenceCache:
|
|
207
|
+
"""Default presence cache using a thread-safe Python set."""
|
|
208
|
+
|
|
209
|
+
def __init__(self) -> None:
|
|
210
|
+
self._keys: set[int] = set()
|
|
211
|
+
|
|
212
|
+
def add(self, key: int) -> None:
|
|
213
|
+
self._keys.add(key)
|
|
214
|
+
|
|
215
|
+
def discard(self, key: int) -> None:
|
|
216
|
+
self._keys.discard(key)
|
|
217
|
+
|
|
218
|
+
def contains(self, key: int) -> bool:
|
|
219
|
+
return key in self._keys
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
PresenceCache = Union[SetPresenceCache]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class NixlDesc:
|
|
227
|
+
device_id: int
|
|
228
|
+
meta_info: str
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass
|
|
232
|
+
class NixlKeyMetadata:
|
|
233
|
+
index: int
|
|
234
|
+
shape: Optional[torch.Size] = None
|
|
235
|
+
dtype: Optional[torch.dtype] = None
|
|
236
|
+
fmt: Optional[MemoryFormat] = None
|
|
237
|
+
pin_count: int = 0
|
|
238
|
+
|
|
239
|
+
def pin(self) -> bool:
|
|
240
|
+
self.pin_count += 1
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
def unpin(self) -> bool:
|
|
244
|
+
self.pin_count -= 1
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def is_pinned(self) -> bool:
|
|
249
|
+
return self.pin_count > 0
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def can_evict(self) -> bool:
|
|
253
|
+
"""
|
|
254
|
+
Check if the related key can be evicted.
|
|
255
|
+
"""
|
|
256
|
+
return not self.is_pinned
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class NixlStorageAgent(ABC):
|
|
260
|
+
agent_name: str
|
|
261
|
+
nixl_agent: NixlAgent
|
|
262
|
+
mem_reg_descs: nixlBind.nixlRegDList
|
|
263
|
+
mem_xfer_handler: NixlDlistHandle
|
|
264
|
+
|
|
265
|
+
def __init__(
|
|
266
|
+
self,
|
|
267
|
+
allocator: PagedTensorMemoryAllocator,
|
|
268
|
+
device: str,
|
|
269
|
+
backend: str,
|
|
270
|
+
backend_params: dict[str, str],
|
|
271
|
+
):
|
|
272
|
+
buffer_ptr = allocator.buffer_ptr
|
|
273
|
+
buffer_size = allocator.buffer_size
|
|
274
|
+
page_size = allocator.align_bytes
|
|
275
|
+
|
|
276
|
+
self.backend = backend
|
|
277
|
+
self.agent_name = "NixlAgent_" + str(uuid.uuid4())
|
|
278
|
+
nixl_conf = NixlAgentConfig(backends=[])
|
|
279
|
+
self.nixl_agent = NixlAgent(self.agent_name, nixl_conf)
|
|
280
|
+
self.nixl_agent.create_backend(backend, backend_params)
|
|
281
|
+
|
|
282
|
+
device_id = torch.cuda.current_device()
|
|
283
|
+
self.init_mem_handlers(device, buffer_ptr, buffer_size, page_size, device_id)
|
|
284
|
+
|
|
285
|
+
def init_mem_handlers(self, device, buffer_ptr, buffer_size, page_size, device_id):
|
|
286
|
+
# Break the registration into page size chunks to ensure the maximum buffer size
|
|
287
|
+
# of the underlying plugin is not exceeded
|
|
288
|
+
reg_list = [
|
|
289
|
+
(base_addr, page_size, device_id, "")
|
|
290
|
+
for base_addr in range(buffer_ptr, buffer_ptr + buffer_size, page_size)
|
|
291
|
+
]
|
|
292
|
+
xfer_desc = [
|
|
293
|
+
(base_addr, page_size, device_id)
|
|
294
|
+
for base_addr in range(buffer_ptr, buffer_ptr + buffer_size, page_size)
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
if device == "cpu":
|
|
298
|
+
mem_type = "DRAM"
|
|
299
|
+
else:
|
|
300
|
+
mem_type = "VRAM"
|
|
301
|
+
|
|
302
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type=mem_type)
|
|
303
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type=mem_type)
|
|
304
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
305
|
+
"", xfer_descs, mem_type=mem_type
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
self.mem_reg_descs = reg_descs
|
|
309
|
+
self.mem_xfer_handler = xfer_handler
|
|
310
|
+
|
|
311
|
+
def get_mem_to_storage_handle(
|
|
312
|
+
self, mem_indices, storage_xfer_handler, storage_indices
|
|
313
|
+
) -> NixlXferHandle:
|
|
314
|
+
return self.nixl_agent.make_prepped_xfer(
|
|
315
|
+
"WRITE",
|
|
316
|
+
self.mem_xfer_handler,
|
|
317
|
+
mem_indices,
|
|
318
|
+
storage_xfer_handler,
|
|
319
|
+
storage_indices,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def get_storage_to_mem_handle(
|
|
323
|
+
self, mem_indices, storage_xfer_handler, storage_indices
|
|
324
|
+
) -> NixlXferHandle:
|
|
325
|
+
return self.nixl_agent.make_prepped_xfer(
|
|
326
|
+
"READ",
|
|
327
|
+
self.mem_xfer_handler,
|
|
328
|
+
mem_indices,
|
|
329
|
+
storage_xfer_handler,
|
|
330
|
+
storage_indices,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def post_blocking(self, handle: NixlXferHandle):
|
|
334
|
+
state = self.nixl_agent.transfer(handle)
|
|
335
|
+
|
|
336
|
+
while state != "DONE" and state != "ERR":
|
|
337
|
+
try:
|
|
338
|
+
state = self.nixl_agent.check_xfer_state(handle)
|
|
339
|
+
except nixlBind.nixlBackendError:
|
|
340
|
+
raise
|
|
341
|
+
|
|
342
|
+
if state == "ERR":
|
|
343
|
+
raise RuntimeError("NIXL transfer failed")
|
|
344
|
+
|
|
345
|
+
def release_handle(self, handle):
|
|
346
|
+
self.nixl_agent.release_xfer_handle(handle)
|
|
347
|
+
|
|
348
|
+
@abstractmethod
|
|
349
|
+
def close(self):
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class NixlStaticStorageAgent(NixlStorageAgent):
|
|
354
|
+
pool: NixlDescPool
|
|
355
|
+
storage_reg_descs: nixlBind.nixlRegDList
|
|
356
|
+
storage_xfer_descs: nixlBind.nixlXferDList
|
|
357
|
+
storage_xfer_handler: NixlDlistHandle
|
|
358
|
+
|
|
359
|
+
def __init__(
|
|
360
|
+
self,
|
|
361
|
+
allocator: PagedTensorMemoryAllocator,
|
|
362
|
+
pool: NixlDescPool,
|
|
363
|
+
device: str,
|
|
364
|
+
backend: str,
|
|
365
|
+
backend_params: dict[str, str],
|
|
366
|
+
):
|
|
367
|
+
super().__init__(allocator, device, backend, backend_params)
|
|
368
|
+
|
|
369
|
+
page_size = allocator.align_bytes
|
|
370
|
+
|
|
371
|
+
if isinstance(pool, NixlFilePool):
|
|
372
|
+
self.init_storage_handlers_file(page_size, pool.fds)
|
|
373
|
+
elif isinstance(pool, NixlObjectPool):
|
|
374
|
+
self.init_storage_handlers_object(page_size, pool.keys)
|
|
375
|
+
else:
|
|
376
|
+
raise TypeError(f"Unsupported pool type: {type(pool).__name__}")
|
|
377
|
+
|
|
378
|
+
def init_storage_handlers_file(self, page_size, fds):
|
|
379
|
+
reg_list = []
|
|
380
|
+
xfer_desc = []
|
|
381
|
+
for fd in fds:
|
|
382
|
+
reg_list.append((0, page_size, fd, ""))
|
|
383
|
+
xfer_desc.append((0, page_size, fd))
|
|
384
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type="FILE")
|
|
385
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type="FILE")
|
|
386
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
387
|
+
self.agent_name, xfer_desc, mem_type="FILE"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
self.storage_reg_descs = reg_descs
|
|
391
|
+
self.storage_xfer_descs = xfer_descs
|
|
392
|
+
self.storage_xfer_handler = xfer_handler
|
|
393
|
+
|
|
394
|
+
def init_storage_handlers_object(self, page_size, keys):
|
|
395
|
+
reg_list = []
|
|
396
|
+
xfer_desc = []
|
|
397
|
+
for i, key in enumerate(keys):
|
|
398
|
+
reg_list.append((0, page_size, i, key))
|
|
399
|
+
xfer_desc.append((0, page_size, i))
|
|
400
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type="OBJ")
|
|
401
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type="OBJ")
|
|
402
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
403
|
+
self.agent_name, xfer_desc, mem_type="OBJ"
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
self.storage_reg_descs = reg_descs
|
|
407
|
+
self.storage_xfer_descs = xfer_descs
|
|
408
|
+
self.storage_xfer_handler = xfer_handler
|
|
409
|
+
|
|
410
|
+
def get_mem_to_storage_handle(self, mem_indices, storage_indices) -> NixlXferHandle: # type: ignore[override]
|
|
411
|
+
return super().get_mem_to_storage_handle(
|
|
412
|
+
mem_indices, self.storage_xfer_handler, storage_indices
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def get_storage_to_mem_handle(self, mem_indices, storage_indices) -> NixlXferHandle: # type: ignore[override]
|
|
416
|
+
return super().get_storage_to_mem_handle(
|
|
417
|
+
mem_indices, self.storage_xfer_handler, storage_indices
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
def close(self):
|
|
421
|
+
self.nixl_agent.release_dlist_handle(self.storage_xfer_handler)
|
|
422
|
+
self.nixl_agent.release_dlist_handle(self.mem_xfer_handler)
|
|
423
|
+
self.nixl_agent.deregister_memory(self.storage_reg_descs)
|
|
424
|
+
self.nixl_agent.deregister_memory(self.mem_reg_descs)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class NixlDynamicStorageAgent(NixlStorageAgent):
|
|
428
|
+
def __init__(
|
|
429
|
+
self,
|
|
430
|
+
allocator: PagedTensorMemoryAllocator,
|
|
431
|
+
device: str,
|
|
432
|
+
backend: str,
|
|
433
|
+
backend_params: dict[str, str],
|
|
434
|
+
):
|
|
435
|
+
super().__init__(allocator, device, backend, backend_params)
|
|
436
|
+
|
|
437
|
+
if backend == "OBJ":
|
|
438
|
+
self.mem_type = "OBJ"
|
|
439
|
+
else:
|
|
440
|
+
# Already validated in validate_nixl_backend
|
|
441
|
+
raise ValueError(f"unexpected backend: {backend}")
|
|
442
|
+
|
|
443
|
+
def create_batched_storage_handler(self, descs: list[NixlDesc], page_size: int):
|
|
444
|
+
reg_list = []
|
|
445
|
+
xfer_desc = []
|
|
446
|
+
|
|
447
|
+
for i in range(len(descs)):
|
|
448
|
+
reg_list.append((0, page_size, descs[i].device_id, descs[i].meta_info))
|
|
449
|
+
xfer_desc.append((0, page_size, descs[i].device_id))
|
|
450
|
+
|
|
451
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, self.mem_type)
|
|
452
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, self.mem_type)
|
|
453
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
454
|
+
self.agent_name, xfer_descs, mem_type=self.mem_type
|
|
455
|
+
)
|
|
456
|
+
return reg_descs, xfer_handler
|
|
457
|
+
|
|
458
|
+
def post_async(self, handle: NixlXferHandle):
|
|
459
|
+
"""Non-blocking async post for WRITE operations."""
|
|
460
|
+
state = self.nixl_agent.transfer(handle)
|
|
461
|
+
return state
|
|
462
|
+
|
|
463
|
+
def release_storage_handler(self, reg_descs, xfer_handler):
|
|
464
|
+
"""Release storage handler resources"""
|
|
465
|
+
self.nixl_agent.release_dlist_handle(xfer_handler)
|
|
466
|
+
self.nixl_agent.deregister_memory(reg_descs)
|
|
467
|
+
|
|
468
|
+
def nixl_desc_exists(self, meta_info: str) -> bool:
|
|
469
|
+
reg_list = [(0, 0, 0, meta_info)]
|
|
470
|
+
try:
|
|
471
|
+
resp = self.nixl_agent.query_memory(
|
|
472
|
+
reg_list, self.backend, mem_type=self.mem_type
|
|
473
|
+
)
|
|
474
|
+
# nixl api query_memory returns a list of nixlRegDesc
|
|
475
|
+
if resp[0] is None:
|
|
476
|
+
return False
|
|
477
|
+
return True
|
|
478
|
+
except Exception as exc:
|
|
479
|
+
logger.warning(f"NIXL Desc {meta_info} query failed: {exc}")
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
def close(self):
|
|
483
|
+
self.nixl_agent.release_dlist_handle(self.mem_xfer_handler)
|
|
484
|
+
self.nixl_agent.deregister_memory(self.mem_reg_descs)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class NixlStorageBackend(AllocatorBackendInterface, ABC):
|
|
488
|
+
"""
|
|
489
|
+
Implementation of the StorageBackendInterface for Nixl.
|
|
490
|
+
|
|
491
|
+
Currently, the put is synchronized and blocking, to simplify the
|
|
492
|
+
implementation.
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
def __init__(
|
|
496
|
+
self,
|
|
497
|
+
nixl_config: NixlStorageConfig,
|
|
498
|
+
config: LMCacheEngineConfig,
|
|
499
|
+
metadata: LMCacheMetadata,
|
|
500
|
+
loop: asyncio.AbstractEventLoop,
|
|
501
|
+
):
|
|
502
|
+
"""
|
|
503
|
+
Initialize the Nixl storage backend.
|
|
504
|
+
|
|
505
|
+
:param dst_device: the device where the blocking retrieved KV is stored,
|
|
506
|
+
could be either "cpu", "cuda", or "cuda:0", "cuda:1", etc.
|
|
507
|
+
"""
|
|
508
|
+
super().__init__(dst_device=nixl_config.buffer_device)
|
|
509
|
+
|
|
510
|
+
self.loop = loop
|
|
511
|
+
self.key_lock = threading.RLock()
|
|
512
|
+
|
|
513
|
+
self.progress_lock = threading.RLock()
|
|
514
|
+
self.progress_set: Set[CacheEngineKey] = set()
|
|
515
|
+
|
|
516
|
+
self.memory_allocator = self.initialize_allocator(config, metadata)
|
|
517
|
+
|
|
518
|
+
def initialize_allocator(
|
|
519
|
+
self,
|
|
520
|
+
config: LMCacheEngineConfig,
|
|
521
|
+
metadata: LMCacheMetadata,
|
|
522
|
+
) -> PagedTensorMemoryAllocator:
|
|
523
|
+
extra_config = config.extra_config
|
|
524
|
+
enable_nixl_storage = extra_config is not None and extra_config.get(
|
|
525
|
+
"enable_nixl_storage"
|
|
526
|
+
)
|
|
527
|
+
assert enable_nixl_storage
|
|
528
|
+
corrected_device = get_correct_device(
|
|
529
|
+
config.nixl_buffer_device,
|
|
530
|
+
metadata.worker_id,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if corrected_device == "cpu":
|
|
534
|
+
self.buffer = _allocate_cpu_memory(config.nixl_buffer_size)
|
|
535
|
+
self.free_pinned_buffer = True
|
|
536
|
+
else:
|
|
537
|
+
base_buffer, self.buffer = _allocate_gpu_memory(
|
|
538
|
+
config.nixl_buffer_size, corrected_device
|
|
539
|
+
)
|
|
540
|
+
torch.cuda.set_device(corrected_device)
|
|
541
|
+
self.base_buffer = base_buffer # Prevents early GC of the aligned tensor.
|
|
542
|
+
self.free_pinned_buffer = False
|
|
543
|
+
|
|
544
|
+
return PagedTensorMemoryAllocator(
|
|
545
|
+
self.buffer,
|
|
546
|
+
[torch.Size(metadata.kv_shape)],
|
|
547
|
+
[metadata.kv_dtype],
|
|
548
|
+
MemoryFormat.KV_2LTD,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
def get_memory_allocator(self):
|
|
552
|
+
return self.memory_allocator
|
|
553
|
+
|
|
554
|
+
def allocate(
|
|
555
|
+
self,
|
|
556
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
557
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
558
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
559
|
+
eviction: bool = True,
|
|
560
|
+
busy_loop: bool = True,
|
|
561
|
+
) -> Optional[MemoryObj]:
|
|
562
|
+
if busy_loop:
|
|
563
|
+
logger.warning("NixlStorageBackend does not support busy loop for now")
|
|
564
|
+
|
|
565
|
+
return self.memory_allocator.allocate(shapes, dtypes, fmt)
|
|
566
|
+
|
|
567
|
+
def batched_allocate(
|
|
568
|
+
self,
|
|
569
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
570
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
571
|
+
batch_size: int,
|
|
572
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
573
|
+
eviction: bool = True,
|
|
574
|
+
busy_loop: bool = True,
|
|
575
|
+
) -> Optional[list[MemoryObj]]:
|
|
576
|
+
if busy_loop:
|
|
577
|
+
logger.warning("NixlStorageBackend does not support busy loop for now")
|
|
578
|
+
|
|
579
|
+
return self.memory_allocator.batched_allocate(shapes, dtypes, batch_size, fmt)
|
|
580
|
+
|
|
581
|
+
def get_allocator_backend(self):
|
|
582
|
+
return self
|
|
583
|
+
|
|
584
|
+
@abstractmethod
|
|
585
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
586
|
+
pass
|
|
587
|
+
|
|
588
|
+
@abstractmethod
|
|
589
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
@abstractmethod
|
|
593
|
+
def batched_submit_put_task(
|
|
594
|
+
self,
|
|
595
|
+
keys: Sequence[CacheEngineKey],
|
|
596
|
+
memory_objs: List[MemoryObj],
|
|
597
|
+
transfer_spec: Any = None,
|
|
598
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
599
|
+
) -> None:
|
|
600
|
+
pass
|
|
601
|
+
|
|
602
|
+
@abstractmethod
|
|
603
|
+
def get_blocking(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
@abstractmethod
|
|
607
|
+
async def batched_get_non_blocking(
|
|
608
|
+
self,
|
|
609
|
+
lookup_id: str,
|
|
610
|
+
keys: list[CacheEngineKey],
|
|
611
|
+
transfer_spec: Any = None,
|
|
612
|
+
) -> list[MemoryObj]:
|
|
613
|
+
pass
|
|
614
|
+
|
|
615
|
+
@abstractmethod
|
|
616
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
|
|
617
|
+
pass
|
|
618
|
+
|
|
619
|
+
@abstractmethod
|
|
620
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
621
|
+
pass
|
|
622
|
+
|
|
623
|
+
@abstractmethod
|
|
624
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
625
|
+
pass
|
|
626
|
+
|
|
627
|
+
@abstractmethod
|
|
628
|
+
def close(self) -> None:
|
|
629
|
+
pass
|
|
630
|
+
|
|
631
|
+
@staticmethod
|
|
632
|
+
def CreateNixlStorageBackend(
|
|
633
|
+
config: LMCacheEngineConfig,
|
|
634
|
+
loop: asyncio.AbstractEventLoop,
|
|
635
|
+
metadata: LMCacheMetadata,
|
|
636
|
+
):
|
|
637
|
+
"""
|
|
638
|
+
Create a Nixl backend with the given configuration.
|
|
639
|
+
|
|
640
|
+
:param nixl_config: The Nixl configuration.
|
|
641
|
+
:param dst_device: The device where the data is stored.
|
|
642
|
+
|
|
643
|
+
:return: A NixlBackend instance.
|
|
644
|
+
"""
|
|
645
|
+
# Create the Nixl config
|
|
646
|
+
nixl_config = NixlStorageConfig.from_cache_engine_config(config, metadata)
|
|
647
|
+
# Create the Nixl backend
|
|
648
|
+
if nixl_config.dynamic_storage:
|
|
649
|
+
return NixlDynamicStorageBackend(nixl_config, config, metadata, loop)
|
|
650
|
+
else:
|
|
651
|
+
return NixlStaticStorageBackend(nixl_config, config, metadata, loop)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class NixlStaticStorageBackend(NixlStorageBackend):
|
|
655
|
+
def __init__(
|
|
656
|
+
self,
|
|
657
|
+
nixl_config: NixlStorageConfig,
|
|
658
|
+
config: LMCacheEngineConfig,
|
|
659
|
+
metadata: LMCacheMetadata,
|
|
660
|
+
loop: asyncio.AbstractEventLoop,
|
|
661
|
+
):
|
|
662
|
+
super().__init__(nixl_config, config, metadata, loop)
|
|
663
|
+
|
|
664
|
+
self.cache_policy = get_cache_policy(config.cache_policy)
|
|
665
|
+
self.key_dict = self.cache_policy.init_mutable_mapping()
|
|
666
|
+
|
|
667
|
+
self.pool = self.createPool(
|
|
668
|
+
nixl_config.backend,
|
|
669
|
+
nixl_config.pool_size,
|
|
670
|
+
nixl_config.path,
|
|
671
|
+
nixl_config.use_direct_io,
|
|
672
|
+
)
|
|
673
|
+
assert self.pool is not None
|
|
674
|
+
|
|
675
|
+
self.agent = NixlStaticStorageAgent(
|
|
676
|
+
self.memory_allocator,
|
|
677
|
+
self.pool,
|
|
678
|
+
nixl_config.buffer_device,
|
|
679
|
+
nixl_config.backend,
|
|
680
|
+
nixl_config.backend_params,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
@staticmethod
|
|
684
|
+
def createPool(backend: str, size: int, path: str, use_direct_io: bool):
|
|
685
|
+
if backend in ("GDS", "GDS_MT", "POSIX", "HF3FS"):
|
|
686
|
+
return NixlFilePool(size, path, use_direct_io)
|
|
687
|
+
elif backend in ("OBJ"):
|
|
688
|
+
return NixlObjectPool(size)
|
|
689
|
+
else:
|
|
690
|
+
raise ValueError(f"Unsupported NIXL backend: {backend}")
|
|
691
|
+
|
|
692
|
+
def add_key_to_dict(
|
|
693
|
+
self, key: CacheEngineKey, obj: MemoryObjMetadata, index: int
|
|
694
|
+
) -> None:
|
|
695
|
+
with self.key_lock:
|
|
696
|
+
assert key not in self.key_dict
|
|
697
|
+
self.key_dict[key] = NixlKeyMetadata(
|
|
698
|
+
shape=obj.shape,
|
|
699
|
+
dtype=obj.dtype,
|
|
700
|
+
fmt=obj.fmt,
|
|
701
|
+
index=index,
|
|
702
|
+
)
|
|
703
|
+
self.cache_policy.update_on_put(key)
|
|
704
|
+
|
|
705
|
+
async def mem_to_storage(
|
|
706
|
+
self, keys: Sequence[CacheEngineKey], mem_objs: List[MemoryObj]
|
|
707
|
+
) -> None:
|
|
708
|
+
mem_indices = [mem_obj.meta.address for mem_obj in mem_objs]
|
|
709
|
+
|
|
710
|
+
storage_indices = []
|
|
711
|
+
for i in range(len(keys)):
|
|
712
|
+
index = self.pool.pop()
|
|
713
|
+
storage_indices.append(index)
|
|
714
|
+
self.add_key_to_dict(keys[i], mem_objs[i].meta, index)
|
|
715
|
+
|
|
716
|
+
handle = self.agent.get_mem_to_storage_handle(mem_indices, storage_indices)
|
|
717
|
+
self.agent.post_blocking(handle)
|
|
718
|
+
self.agent.release_handle(handle)
|
|
719
|
+
|
|
720
|
+
for key in keys:
|
|
721
|
+
with self.progress_lock:
|
|
722
|
+
self.progress_set.discard(key)
|
|
723
|
+
|
|
724
|
+
def _collect_metadata_with_lock(
|
|
725
|
+
self, keys: list[CacheEngineKey]
|
|
726
|
+
) -> list[Optional[NixlKeyMetadata]]:
|
|
727
|
+
"""
|
|
728
|
+
Fast metadata collection with lock.
|
|
729
|
+
Returns metadata for each key, None if key doesn't exist.
|
|
730
|
+
"""
|
|
731
|
+
metadata_list: list[Optional[NixlKeyMetadata]] = []
|
|
732
|
+
with self.key_lock:
|
|
733
|
+
for key in keys:
|
|
734
|
+
metadata = self.key_dict.get(key)
|
|
735
|
+
if metadata is not None:
|
|
736
|
+
self.cache_policy.update_on_hit(key, self.key_dict)
|
|
737
|
+
metadata_list.append(metadata)
|
|
738
|
+
return metadata_list
|
|
739
|
+
|
|
740
|
+
async def _nixl_transfer_async(
|
|
741
|
+
self, metadata_list: list[Optional[NixlKeyMetadata]]
|
|
742
|
+
) -> list[Optional[MemoryObj]]:
|
|
743
|
+
"""
|
|
744
|
+
Memory allocation and NIXL transfer without locks.
|
|
745
|
+
Can run async and in parallel with other transfers.
|
|
746
|
+
"""
|
|
747
|
+
obj_list: list[Optional[MemoryObj]] = []
|
|
748
|
+
mem_indices = []
|
|
749
|
+
storage_indices = []
|
|
750
|
+
|
|
751
|
+
# Memory allocation outside lock
|
|
752
|
+
for metadata in metadata_list:
|
|
753
|
+
if metadata is None:
|
|
754
|
+
obj_list.append(None)
|
|
755
|
+
continue
|
|
756
|
+
|
|
757
|
+
dtype = metadata.dtype
|
|
758
|
+
shape = metadata.shape
|
|
759
|
+
fmt = metadata.fmt
|
|
760
|
+
assert dtype is not None
|
|
761
|
+
assert shape is not None
|
|
762
|
+
assert fmt is not None
|
|
763
|
+
|
|
764
|
+
obj = self.memory_allocator.allocate(shape, dtype, fmt)
|
|
765
|
+
assert obj is not None
|
|
766
|
+
|
|
767
|
+
obj_list.append(obj)
|
|
768
|
+
|
|
769
|
+
mem_indices.append(obj.meta.address)
|
|
770
|
+
storage_indices.append(metadata.index)
|
|
771
|
+
|
|
772
|
+
if not mem_indices:
|
|
773
|
+
return obj_list
|
|
774
|
+
|
|
775
|
+
handle = self.agent.get_storage_to_mem_handle(mem_indices, storage_indices)
|
|
776
|
+
self.agent.post_blocking(handle)
|
|
777
|
+
self.agent.release_handle(handle)
|
|
778
|
+
|
|
779
|
+
return obj_list
|
|
780
|
+
|
|
781
|
+
async def storage_to_mem(
|
|
782
|
+
self, keys: list[CacheEngineKey]
|
|
783
|
+
) -> list[Optional[MemoryObj]]:
|
|
784
|
+
"""
|
|
785
|
+
Combined method: collect metadata with lock, then do NIXL transfer.
|
|
786
|
+
"""
|
|
787
|
+
metadata_list = self._collect_metadata_with_lock(keys)
|
|
788
|
+
return await self._nixl_transfer_async(metadata_list)
|
|
789
|
+
|
|
790
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
791
|
+
"""
|
|
792
|
+
Check whether key is in the storage backend.
|
|
793
|
+
|
|
794
|
+
:param key: The key to check
|
|
795
|
+
:param pin: Whether to pin the object in the backend.
|
|
796
|
+
|
|
797
|
+
:return: True if the key exists, False otherwise
|
|
798
|
+
"""
|
|
799
|
+
|
|
800
|
+
with self.key_lock:
|
|
801
|
+
if key in self.key_dict:
|
|
802
|
+
if pin:
|
|
803
|
+
self.key_dict[key].pin()
|
|
804
|
+
return True
|
|
805
|
+
else:
|
|
806
|
+
return False
|
|
807
|
+
|
|
808
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
809
|
+
"""
|
|
810
|
+
Check whether key is in the ongoing submit_put_task tasks.
|
|
811
|
+
|
|
812
|
+
:param key: The key to check
|
|
813
|
+
:return: True if the key exists in put tasks, False otherwise
|
|
814
|
+
"""
|
|
815
|
+
with self.progress_lock:
|
|
816
|
+
return key in self.progress_set
|
|
817
|
+
|
|
818
|
+
def batched_submit_put_task(
|
|
819
|
+
self,
|
|
820
|
+
keys: Sequence[CacheEngineKey],
|
|
821
|
+
memory_objs: List[MemoryObj],
|
|
822
|
+
transfer_spec: Any = None,
|
|
823
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
824
|
+
) -> None:
|
|
825
|
+
"""
|
|
826
|
+
:param on_complete_callback: Optional callback (not yet supported for
|
|
827
|
+
NixlCacheBackend async operations).
|
|
828
|
+
"""
|
|
829
|
+
with self.key_lock:
|
|
830
|
+
available_descs = self.pool.get_num_available_descs()
|
|
831
|
+
num_evict = len(keys) - available_descs
|
|
832
|
+
if num_evict > 0:
|
|
833
|
+
evict_keys = self.cache_policy.get_evict_candidates(
|
|
834
|
+
self.key_dict, num_candidates=num_evict
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
if not evict_keys:
|
|
838
|
+
logger.warning(
|
|
839
|
+
"No eviction candidates found. Backend under pressure."
|
|
840
|
+
)
|
|
841
|
+
return None
|
|
842
|
+
|
|
843
|
+
self.batched_remove(evict_keys, force=False)
|
|
844
|
+
|
|
845
|
+
with self.progress_lock:
|
|
846
|
+
for key in keys:
|
|
847
|
+
self.progress_set.add(key)
|
|
848
|
+
|
|
849
|
+
asyncio.run_coroutine_threadsafe(
|
|
850
|
+
self.mem_to_storage(keys, memory_objs), self.loop
|
|
851
|
+
)
|
|
852
|
+
# TODO: Add callback support for async NIXL operations
|
|
853
|
+
|
|
854
|
+
def get_blocking(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
855
|
+
"""
|
|
856
|
+
A blocking function to get the kv cache from the storage backend.
|
|
857
|
+
|
|
858
|
+
:param key: The key of the MemoryObj.
|
|
859
|
+
|
|
860
|
+
:return: MemoryObj. None if the key does not exist.
|
|
861
|
+
"""
|
|
862
|
+
|
|
863
|
+
future = asyncio.run_coroutine_threadsafe(self.storage_to_mem([key]), self.loop)
|
|
864
|
+
|
|
865
|
+
obj_list = future.result()
|
|
866
|
+
return obj_list[0]
|
|
867
|
+
|
|
868
|
+
def batched_get_blocking(
|
|
869
|
+
self,
|
|
870
|
+
keys: List[CacheEngineKey],
|
|
871
|
+
) -> List[Optional[MemoryObj]]:
|
|
872
|
+
"""
|
|
873
|
+
A blocking function to get the kv cache from the storage backend.
|
|
874
|
+
|
|
875
|
+
:param List[CacheEngineKey] keys: The keys of the MemoryObjs.
|
|
876
|
+
|
|
877
|
+
:return: a list of memory objects.
|
|
878
|
+
"""
|
|
879
|
+
|
|
880
|
+
if not keys:
|
|
881
|
+
return []
|
|
882
|
+
|
|
883
|
+
future = asyncio.run_coroutine_threadsafe(self.storage_to_mem(keys), self.loop)
|
|
884
|
+
|
|
885
|
+
obj_list = future.result()
|
|
886
|
+
return obj_list
|
|
887
|
+
|
|
888
|
+
async def batched_get_non_blocking(
|
|
889
|
+
self,
|
|
890
|
+
lookup_id: str,
|
|
891
|
+
keys: list[CacheEngineKey],
|
|
892
|
+
transfer_spec: Any = None,
|
|
893
|
+
) -> list[MemoryObj]:
|
|
894
|
+
obj_list = await self.storage_to_mem(keys)
|
|
895
|
+
assert None not in obj_list
|
|
896
|
+
return cast(list[MemoryObj], obj_list)
|
|
897
|
+
|
|
898
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
|
|
899
|
+
"""
|
|
900
|
+
Remove the key from the storage backend.
|
|
901
|
+
|
|
902
|
+
:param key: The key to remove.
|
|
903
|
+
"""
|
|
904
|
+
|
|
905
|
+
with self.key_lock:
|
|
906
|
+
metadata = self.key_dict.pop(key, None)
|
|
907
|
+
if metadata is None:
|
|
908
|
+
return False
|
|
909
|
+
if force:
|
|
910
|
+
self.cache_policy.update_on_force_evict(key)
|
|
911
|
+
|
|
912
|
+
self.pool.push(metadata.index)
|
|
913
|
+
return True
|
|
914
|
+
|
|
915
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
916
|
+
with self.key_lock:
|
|
917
|
+
if key in self.key_dict:
|
|
918
|
+
self.key_dict[key].pin()
|
|
919
|
+
return True
|
|
920
|
+
else:
|
|
921
|
+
return False
|
|
922
|
+
|
|
923
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
924
|
+
with self.key_lock:
|
|
925
|
+
if key in self.key_dict:
|
|
926
|
+
self.key_dict[key].unpin()
|
|
927
|
+
return True
|
|
928
|
+
else:
|
|
929
|
+
return False
|
|
930
|
+
|
|
931
|
+
def close(self) -> None:
|
|
932
|
+
"""
|
|
933
|
+
Close the storage backend.
|
|
934
|
+
"""
|
|
935
|
+
self.agent.close()
|
|
936
|
+
self.pool.close()
|
|
937
|
+
self.memory_allocator.close()
|
|
938
|
+
|
|
939
|
+
if self.free_pinned_buffer:
|
|
940
|
+
_free_cpu_memory(self.buffer)
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
class NixlDynamicStorageBackend(NixlStorageBackend):
|
|
944
|
+
def __init__(
|
|
945
|
+
self,
|
|
946
|
+
nixl_config: NixlStorageConfig,
|
|
947
|
+
config: LMCacheEngineConfig,
|
|
948
|
+
metadata: LMCacheMetadata,
|
|
949
|
+
loop: asyncio.AbstractEventLoop,
|
|
950
|
+
cache_policy: Optional[PresenceCache] = None,
|
|
951
|
+
):
|
|
952
|
+
super().__init__(nixl_config, config, metadata, loop)
|
|
953
|
+
|
|
954
|
+
self.async_mode = nixl_config.enable_async_put
|
|
955
|
+
self.enable_presence_cache = nixl_config.enable_presence_cache
|
|
956
|
+
|
|
957
|
+
# Presence cache to reduce remote contains checks
|
|
958
|
+
self.hit_counter = 0
|
|
959
|
+
self.total_counter = 0
|
|
960
|
+
self.key_presence_cache: Optional[PresenceCache] = None
|
|
961
|
+
if self.enable_presence_cache:
|
|
962
|
+
self.key_presence_cache = (
|
|
963
|
+
cache_policy if cache_policy is not None else SetPresenceCache()
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
# Initialize metadata from config
|
|
967
|
+
self.meta_shape: Optional[torch.Size] = None
|
|
968
|
+
self.meta_dtype: Optional[torch.dtype] = None
|
|
969
|
+
self.meta_fmt: Optional[MemoryFormat] = None
|
|
970
|
+
self.init_chunk_meta(metadata)
|
|
971
|
+
|
|
972
|
+
self.agent = NixlDynamicStorageAgent(
|
|
973
|
+
self.memory_allocator,
|
|
974
|
+
nixl_config.buffer_device,
|
|
975
|
+
nixl_config.backend,
|
|
976
|
+
nixl_config.backend_params,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
def set_presence_cache(self, cache: PresenceCache) -> None:
|
|
980
|
+
"""Configure a custom cache policy for key presence tracking."""
|
|
981
|
+
if self.enable_presence_cache:
|
|
982
|
+
self.key_presence_cache = cache
|
|
983
|
+
|
|
984
|
+
def _cache_contains(self, chunk_hash: int) -> bool:
|
|
985
|
+
if not self.enable_presence_cache or self.key_presence_cache is None:
|
|
986
|
+
return False
|
|
987
|
+
found = self.key_presence_cache.contains(chunk_hash)
|
|
988
|
+
self.hit_counter += 1 if found else 0
|
|
989
|
+
self.total_counter += 1
|
|
990
|
+
if self.total_counter % 100 == 0:
|
|
991
|
+
logger.debug(f"Cache hit: {self.hit_counter} vs {self.total_counter}")
|
|
992
|
+
return found
|
|
993
|
+
|
|
994
|
+
def _cache_add(self, chunk_hash: int) -> None:
|
|
995
|
+
if not self.enable_presence_cache or self.key_presence_cache is None:
|
|
996
|
+
return
|
|
997
|
+
self.key_presence_cache.add(chunk_hash)
|
|
998
|
+
|
|
999
|
+
def _cache_discard(self, chunk_hash: int) -> None:
|
|
1000
|
+
if not self.enable_presence_cache or self.key_presence_cache is None:
|
|
1001
|
+
return
|
|
1002
|
+
self.key_presence_cache.discard(chunk_hash)
|
|
1003
|
+
|
|
1004
|
+
def init_chunk_meta(
|
|
1005
|
+
self,
|
|
1006
|
+
metadata: Optional[LMCacheMetadata],
|
|
1007
|
+
) -> None:
|
|
1008
|
+
"""Initialize chunk metadata similar to base_connector.init_chunk_meta()"""
|
|
1009
|
+
if metadata is None:
|
|
1010
|
+
return
|
|
1011
|
+
|
|
1012
|
+
self.meta_shape = torch.Size(
|
|
1013
|
+
[
|
|
1014
|
+
metadata.kv_shape[1],
|
|
1015
|
+
metadata.kv_shape[0],
|
|
1016
|
+
metadata.kv_shape[2],
|
|
1017
|
+
metadata.kv_shape[3] * metadata.kv_shape[4],
|
|
1018
|
+
]
|
|
1019
|
+
)
|
|
1020
|
+
self.meta_dtype = metadata.kv_dtype
|
|
1021
|
+
self.meta_fmt = (
|
|
1022
|
+
MemoryFormat.KV_MLA_FMT if metadata.use_mla else MemoryFormat.KV_2LTD
|
|
1023
|
+
)
|
|
1024
|
+
logger.info(
|
|
1025
|
+
f"Initialized nixl object backend metadata: "
|
|
1026
|
+
f"shape: {self.meta_shape}, "
|
|
1027
|
+
f"dtype: {self.meta_dtype}, "
|
|
1028
|
+
f"fmt: {self.meta_fmt}"
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
def _format_object_key(self, key: CacheEngineKey) -> str:
|
|
1032
|
+
"""
|
|
1033
|
+
Generate object key name based on CacheEngineKey information.
|
|
1034
|
+
Similar to s3_connector._format_safe_path()
|
|
1035
|
+
"""
|
|
1036
|
+
key_str = key.to_string()
|
|
1037
|
+
# Replace slashes with underscores to make it safe for object storage
|
|
1038
|
+
flat_key_str = key_str.replace("/", "_").replace("@", "_")
|
|
1039
|
+
# URL encode for safety
|
|
1040
|
+
return url_quote(flat_key_str, safe="")
|
|
1041
|
+
|
|
1042
|
+
def key_exists(self, key: CacheEngineKey) -> bool:
|
|
1043
|
+
if self.agent.mem_type == "OBJ":
|
|
1044
|
+
meta_info = self._format_object_key(key)
|
|
1045
|
+
else:
|
|
1046
|
+
# Already validated in validate_nixl_backend
|
|
1047
|
+
raise ValueError(f"unexpected mem_type: {self.agent.mem_type}")
|
|
1048
|
+
|
|
1049
|
+
return self.agent.nixl_desc_exists(meta_info)
|
|
1050
|
+
|
|
1051
|
+
def storage_to_mem(
|
|
1052
|
+
self, keys: list[CacheEngineKey], pin: bool = False
|
|
1053
|
+
) -> list[Optional[MemoryObj]]:
|
|
1054
|
+
obj_list: list[Optional[MemoryObj]] = []
|
|
1055
|
+
page_size = self.memory_allocator.align_bytes
|
|
1056
|
+
start_time = time.time()
|
|
1057
|
+
storage_indices = []
|
|
1058
|
+
mem_indices = []
|
|
1059
|
+
descs = []
|
|
1060
|
+
|
|
1061
|
+
# Prepare mem and storage indices
|
|
1062
|
+
for idx in range(len(keys)):
|
|
1063
|
+
# Allocate memory for the object
|
|
1064
|
+
assert self.meta_shape is not None
|
|
1065
|
+
assert self.meta_dtype is not None
|
|
1066
|
+
assert self.meta_fmt is not None
|
|
1067
|
+
obj = self.memory_allocator.allocate(
|
|
1068
|
+
self.meta_shape, self.meta_dtype, self.meta_fmt
|
|
1069
|
+
)
|
|
1070
|
+
if obj is None:
|
|
1071
|
+
# free previous allocated objects
|
|
1072
|
+
logger.warning("Failed to allocate memory")
|
|
1073
|
+
for obj in obj_list:
|
|
1074
|
+
self.memory_allocator.free(obj)
|
|
1075
|
+
return [None] * len(keys)
|
|
1076
|
+
|
|
1077
|
+
obj_list.append(obj)
|
|
1078
|
+
mem_indices.append(obj.meta.address)
|
|
1079
|
+
storage_indices.append(idx)
|
|
1080
|
+
|
|
1081
|
+
if self.agent.mem_type == "OBJ":
|
|
1082
|
+
for idx in range(len(keys)):
|
|
1083
|
+
object_key = self._format_object_key(keys[idx])
|
|
1084
|
+
descs.append(NixlDesc(device_id=idx, meta_info=object_key))
|
|
1085
|
+
else:
|
|
1086
|
+
# Already validated in validate_nixl_backend
|
|
1087
|
+
raise ValueError(f"unexpected mem_type: {self.agent.mem_type}")
|
|
1088
|
+
|
|
1089
|
+
# Create batched storage handler
|
|
1090
|
+
storage_reg_descs, storage_xfer_handler = (
|
|
1091
|
+
self.agent.create_batched_storage_handler(descs, page_size)
|
|
1092
|
+
)
|
|
1093
|
+
# Create transfer handle
|
|
1094
|
+
handle = self.agent.get_storage_to_mem_handle(
|
|
1095
|
+
mem_indices, storage_xfer_handler, storage_indices
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
# Try to read the object
|
|
1099
|
+
try:
|
|
1100
|
+
self.agent.post_blocking(handle)
|
|
1101
|
+
xfer_state = True
|
|
1102
|
+
except nixlBind.nixlBackendError as exc:
|
|
1103
|
+
logger.warning(f"Batch Transfer failed: {exc}")
|
|
1104
|
+
# Treat "not found", timeout or other transfer failures as recoverable
|
|
1105
|
+
# Do not raise exception to avoid terminating the program
|
|
1106
|
+
xfer_state = False
|
|
1107
|
+
|
|
1108
|
+
self.agent.release_handle(handle)
|
|
1109
|
+
self.agent.release_storage_handler(storage_reg_descs, storage_xfer_handler)
|
|
1110
|
+
|
|
1111
|
+
if xfer_state:
|
|
1112
|
+
for i in range(len(keys)):
|
|
1113
|
+
key = keys[i]
|
|
1114
|
+
self._cache_add(key.chunk_hash)
|
|
1115
|
+
end_time = time.time()
|
|
1116
|
+
duration = end_time - start_time
|
|
1117
|
+
logger.debug(
|
|
1118
|
+
f"storage_to_mem for {len(keys)} objects size {page_size * len(keys)} "
|
|
1119
|
+
f"took {duration:.6f} seconds"
|
|
1120
|
+
)
|
|
1121
|
+
return obj_list
|
|
1122
|
+
else:
|
|
1123
|
+
# Free the allocated memory and discard cache if transfer failed
|
|
1124
|
+
for obj in obj_list:
|
|
1125
|
+
self.memory_allocator.free(obj)
|
|
1126
|
+
for key in keys:
|
|
1127
|
+
self._cache_discard(key.chunk_hash)
|
|
1128
|
+
return [None] * len(keys)
|
|
1129
|
+
|
|
1130
|
+
async def _wait_for_transfer(
|
|
1131
|
+
self,
|
|
1132
|
+
handle: NixlXferHandle,
|
|
1133
|
+
initial_state: str,
|
|
1134
|
+
keys: Sequence[CacheEngineKey],
|
|
1135
|
+
storage_reg_descs: nixlBind.nixlRegDList,
|
|
1136
|
+
storage_xfer_handler: NixlDlistHandle,
|
|
1137
|
+
mem_objs: List[MemoryObj],
|
|
1138
|
+
):
|
|
1139
|
+
"""Asynchronously wait for transfer to complete without blocking."""
|
|
1140
|
+
state = ""
|
|
1141
|
+
try:
|
|
1142
|
+
state = initial_state
|
|
1143
|
+
while state != "DONE" and state != "ERR":
|
|
1144
|
+
state = self.agent.nixl_agent.check_xfer_state(handle)
|
|
1145
|
+
await asyncio.sleep(0.001) # Avoid busy-waiting, yield to event loop
|
|
1146
|
+
if state == "ERR":
|
|
1147
|
+
raise RuntimeError("NIXL transfer failed")
|
|
1148
|
+
|
|
1149
|
+
finally:
|
|
1150
|
+
# Release the handle after transfer completes (success or failure)
|
|
1151
|
+
self.agent.release_handle(handle)
|
|
1152
|
+
self.agent.release_storage_handler(storage_reg_descs, storage_xfer_handler)
|
|
1153
|
+
|
|
1154
|
+
if state == "DONE":
|
|
1155
|
+
for key in keys:
|
|
1156
|
+
with self.progress_lock:
|
|
1157
|
+
self.progress_set.discard(key)
|
|
1158
|
+
self._cache_add(key.chunk_hash)
|
|
1159
|
+
|
|
1160
|
+
for mem_obj in mem_objs:
|
|
1161
|
+
mem_obj.ref_count_down()
|
|
1162
|
+
|
|
1163
|
+
async def mem_to_storage(
|
|
1164
|
+
self, keys: Sequence[CacheEngineKey], mem_objs: List[MemoryObj]
|
|
1165
|
+
) -> None:
|
|
1166
|
+
start_time = time.time()
|
|
1167
|
+
if len(keys) == 0:
|
|
1168
|
+
return
|
|
1169
|
+
|
|
1170
|
+
storage_indices = range(len(keys))
|
|
1171
|
+
mem_indices = [mem_obj.meta.address for mem_obj in mem_objs]
|
|
1172
|
+
page_size = self.memory_allocator.align_bytes
|
|
1173
|
+
descs = []
|
|
1174
|
+
|
|
1175
|
+
if self.agent.mem_type == "OBJ":
|
|
1176
|
+
for idx in range(len(keys)):
|
|
1177
|
+
object_key = self._format_object_key(keys[idx])
|
|
1178
|
+
descs.append(NixlDesc(device_id=idx, meta_info=object_key))
|
|
1179
|
+
else:
|
|
1180
|
+
# Already validated in validate_nixl_backend
|
|
1181
|
+
raise ValueError(f"unexpected mem_type: {self.agent.mem_type}")
|
|
1182
|
+
|
|
1183
|
+
storage_reg_descs, storage_xfer_handler = (
|
|
1184
|
+
self.agent.create_batched_storage_handler(descs, page_size)
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
handle = self.agent.get_mem_to_storage_handle(
|
|
1188
|
+
mem_indices, storage_xfer_handler, storage_indices
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
if self.async_mode:
|
|
1192
|
+
initial_state = self.agent.post_async(handle)
|
|
1193
|
+
# Submit the async wait to the event loop and return immediately
|
|
1194
|
+
asyncio.create_task(
|
|
1195
|
+
self._wait_for_transfer(
|
|
1196
|
+
handle,
|
|
1197
|
+
initial_state,
|
|
1198
|
+
keys,
|
|
1199
|
+
storage_reg_descs,
|
|
1200
|
+
storage_xfer_handler,
|
|
1201
|
+
mem_objs,
|
|
1202
|
+
)
|
|
1203
|
+
)
|
|
1204
|
+
else:
|
|
1205
|
+
self.agent.post_blocking(handle)
|
|
1206
|
+
self.agent.release_handle(handle)
|
|
1207
|
+
self.agent.release_storage_handler(storage_reg_descs, storage_xfer_handler)
|
|
1208
|
+
|
|
1209
|
+
end_time = time.time()
|
|
1210
|
+
duration = end_time - start_time
|
|
1211
|
+
logger.debug(
|
|
1212
|
+
f"mem_to_storage for {len(keys)} objects size {page_size * len(keys)} "
|
|
1213
|
+
f"took {duration:.3f} seconds"
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
for key in keys:
|
|
1217
|
+
with self.progress_lock:
|
|
1218
|
+
self.progress_set.discard(key)
|
|
1219
|
+
self._cache_add(key.chunk_hash)
|
|
1220
|
+
|
|
1221
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
1222
|
+
"""
|
|
1223
|
+
Check whether key is in the ongoing submit_put_task tasks.
|
|
1224
|
+
|
|
1225
|
+
:param key: The key to check
|
|
1226
|
+
:return: True if the key exists in put tasks, False otherwise
|
|
1227
|
+
"""
|
|
1228
|
+
with self.progress_lock:
|
|
1229
|
+
return key in self.progress_set
|
|
1230
|
+
|
|
1231
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
1232
|
+
"""
|
|
1233
|
+
Check whether key is in the storage backend.
|
|
1234
|
+
|
|
1235
|
+
This method uses nixl querymem to check existence.
|
|
1236
|
+
If successful, it caches the name for later use.
|
|
1237
|
+
|
|
1238
|
+
:param key: The key to check
|
|
1239
|
+
:param pin: Whether to pin the object in the backend
|
|
1240
|
+
(Not Implemented)
|
|
1241
|
+
|
|
1242
|
+
:return: True if the key exists, False otherwise
|
|
1243
|
+
"""
|
|
1244
|
+
# Check if already in progress
|
|
1245
|
+
if self.exists_in_put_tasks(key):
|
|
1246
|
+
logger.debug(f"Key {key.chunk_hash:x} is in put tasks")
|
|
1247
|
+
return False
|
|
1248
|
+
|
|
1249
|
+
# Check presence cache before hitting remote storage if not prefetching
|
|
1250
|
+
if self._cache_contains(key.chunk_hash):
|
|
1251
|
+
return True
|
|
1252
|
+
|
|
1253
|
+
xfer_state = self.key_exists(key)
|
|
1254
|
+
if xfer_state:
|
|
1255
|
+
self._cache_add(key.chunk_hash)
|
|
1256
|
+
|
|
1257
|
+
return xfer_state
|
|
1258
|
+
|
|
1259
|
+
async def batched_async_contains(
|
|
1260
|
+
self,
|
|
1261
|
+
lookup_id: str,
|
|
1262
|
+
keys: list[CacheEngineKey],
|
|
1263
|
+
pin: bool = False,
|
|
1264
|
+
) -> int:
|
|
1265
|
+
if not keys:
|
|
1266
|
+
return 0
|
|
1267
|
+
"""
|
|
1268
|
+
Nixl API query_memory also supports batched query. However when there
|
|
1269
|
+
are hundreds of keys to be queried and keys in the first few places
|
|
1270
|
+
don't exist, the batched query has to be failed fast.
|
|
1271
|
+
Therefore we implement batched contains() in a managed thread pool,
|
|
1272
|
+
which fails fast when a key doesn't exist.
|
|
1273
|
+
"""
|
|
1274
|
+
n = len(keys)
|
|
1275
|
+
idx = 0
|
|
1276
|
+
batch_size = 16
|
|
1277
|
+
|
|
1278
|
+
while idx < n:
|
|
1279
|
+
batch = keys[idx : idx + batch_size]
|
|
1280
|
+
tasks = [asyncio.to_thread(self.contains, k, pin) for k in batch]
|
|
1281
|
+
results = await asyncio.gather(*tasks, return_exceptions=False)
|
|
1282
|
+
|
|
1283
|
+
# Stop at the first miss
|
|
1284
|
+
for j, ok in enumerate(results):
|
|
1285
|
+
if not ok:
|
|
1286
|
+
return idx + j
|
|
1287
|
+
idx += len(batch)
|
|
1288
|
+
|
|
1289
|
+
return n
|
|
1290
|
+
|
|
1291
|
+
def batched_submit_put_task(
|
|
1292
|
+
self,
|
|
1293
|
+
keys: Sequence[CacheEngineKey],
|
|
1294
|
+
memory_objs: List[MemoryObj],
|
|
1295
|
+
transfer_spec: Any = None,
|
|
1296
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
1297
|
+
) -> None:
|
|
1298
|
+
"""
|
|
1299
|
+
:param on_complete_callback: Optional callback invoked once per key
|
|
1300
|
+
after transfer completes. Only supported in sync mode (async_mode=False).
|
|
1301
|
+
"""
|
|
1302
|
+
with self.progress_lock:
|
|
1303
|
+
for key in keys:
|
|
1304
|
+
self.progress_set.add(key)
|
|
1305
|
+
|
|
1306
|
+
if self.async_mode:
|
|
1307
|
+
for mem_obj in memory_objs:
|
|
1308
|
+
mem_obj.ref_count_up()
|
|
1309
|
+
asyncio.run_coroutine_threadsafe(
|
|
1310
|
+
self.mem_to_storage(keys, memory_objs), self.loop
|
|
1311
|
+
)
|
|
1312
|
+
# Note: callback not supported in async mode
|
|
1313
|
+
else:
|
|
1314
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
1315
|
+
self.mem_to_storage(keys, memory_objs), self.loop
|
|
1316
|
+
)
|
|
1317
|
+
future.result()
|
|
1318
|
+
|
|
1319
|
+
# Call completion callback for sync mode
|
|
1320
|
+
if on_complete_callback is not None:
|
|
1321
|
+
for key in keys:
|
|
1322
|
+
try:
|
|
1323
|
+
on_complete_callback(key)
|
|
1324
|
+
except Exception as e:
|
|
1325
|
+
logger.warning(
|
|
1326
|
+
f"on_complete_callback failed for key {key}: {e}"
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
def get_blocking(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
1330
|
+
"""
|
|
1331
|
+
A blocking function to get the kv cache from the storage backend.
|
|
1332
|
+
|
|
1333
|
+
:param key: The key of the MemoryObj.
|
|
1334
|
+
|
|
1335
|
+
:return: MemoryObj. None if the key does not exist.
|
|
1336
|
+
"""
|
|
1337
|
+
obj_list = self.storage_to_mem([key], False)
|
|
1338
|
+
return obj_list[0]
|
|
1339
|
+
|
|
1340
|
+
def batched_get_blocking(
|
|
1341
|
+
self,
|
|
1342
|
+
keys: List[CacheEngineKey],
|
|
1343
|
+
) -> List[Optional[MemoryObj]]:
|
|
1344
|
+
"""
|
|
1345
|
+
A blocking function to get the kv cache from the storage backend.
|
|
1346
|
+
:param List[CacheEngineKey] keys: The keys of the MemoryObjs.
|
|
1347
|
+
:return: a list of memory objects.
|
|
1348
|
+
"""
|
|
1349
|
+
if not keys:
|
|
1350
|
+
return []
|
|
1351
|
+
|
|
1352
|
+
obj_list = self.storage_to_mem(keys, False)
|
|
1353
|
+
return obj_list
|
|
1354
|
+
|
|
1355
|
+
async def batched_get_non_blocking(
|
|
1356
|
+
self,
|
|
1357
|
+
lookup_id: str,
|
|
1358
|
+
keys: list[CacheEngineKey],
|
|
1359
|
+
transfer_spec: Any = None,
|
|
1360
|
+
) -> list[MemoryObj]:
|
|
1361
|
+
"""
|
|
1362
|
+
Non blocking interface to get the kv cache from the storage backend.
|
|
1363
|
+
:param List[CacheEngineKey] keys: The keys of the MemoryObjs.
|
|
1364
|
+
:return: a list of memory objects.
|
|
1365
|
+
"""
|
|
1366
|
+
obj_list = self.storage_to_mem(keys, False)
|
|
1367
|
+
assert None not in obj_list
|
|
1368
|
+
return cast(list[MemoryObj], obj_list)
|
|
1369
|
+
|
|
1370
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
|
|
1371
|
+
"""
|
|
1372
|
+
Remove the key from the storage backend.
|
|
1373
|
+
|
|
1374
|
+
:param key: The key to remove.
|
|
1375
|
+
:param force: Whether to force removal (not used in this implementation)
|
|
1376
|
+
"""
|
|
1377
|
+
self._cache_discard(key.chunk_hash)
|
|
1378
|
+
return True
|
|
1379
|
+
|
|
1380
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
1381
|
+
"""
|
|
1382
|
+
Not implemented yet
|
|
1383
|
+
"""
|
|
1384
|
+
return False
|
|
1385
|
+
|
|
1386
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
1387
|
+
"""
|
|
1388
|
+
Not implemented yet
|
|
1389
|
+
"""
|
|
1390
|
+
return False
|
|
1391
|
+
|
|
1392
|
+
def close(self) -> None:
|
|
1393
|
+
"""
|
|
1394
|
+
Close the storage backend.
|
|
1395
|
+
"""
|
|
1396
|
+
self.agent.close()
|
|
1397
|
+
self.memory_allocator.close()
|
|
1398
|
+
|
|
1399
|
+
if self.free_pinned_buffer:
|
|
1400
|
+
_free_cpu_memory(self.buffer)
|