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,828 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from enum import IntEnum, auto
|
|
4
|
+
from typing import List, Optional, Tuple, no_type_check
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# Third Party
|
|
10
|
+
from redis.asyncio.cluster import ClusterNode, RedisCluster
|
|
11
|
+
import redis.asyncio as redis
|
|
12
|
+
|
|
13
|
+
# First Party
|
|
14
|
+
from lmcache.logging import init_logger
|
|
15
|
+
from lmcache.utils import CacheEngineKey
|
|
16
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
17
|
+
from lmcache.v1.protocol import RemoteMetadata
|
|
18
|
+
from lmcache.v1.storage_backend.connector.base_connector import RemoteConnector
|
|
19
|
+
from lmcache.v1.storage_backend.job_executor.pq_executor import AsyncPQExecutor
|
|
20
|
+
from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
|
|
21
|
+
from lmcache.v1.storage_backend.native_clients.resp_client import RESPClient
|
|
22
|
+
|
|
23
|
+
logger = init_logger(__name__)
|
|
24
|
+
|
|
25
|
+
# TODO(Jiayi): Use `redis.asyncio`
|
|
26
|
+
# NOTE(Jiayi): `redis-py` supports async operations, but data copy
|
|
27
|
+
# cannot be avoided. `hiredis` is more lower-level but asyncio is
|
|
28
|
+
# not supported.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Priorities(IntEnum):
|
|
32
|
+
PEEK = auto()
|
|
33
|
+
PREFETCH = auto()
|
|
34
|
+
GET = auto()
|
|
35
|
+
PUT = auto()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RESPConnector(RemoteConnector):
|
|
39
|
+
"""
|
|
40
|
+
The remote url should start with "resp://" and only have one host-port pair
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
host: str,
|
|
46
|
+
port: int,
|
|
47
|
+
loop: asyncio.AbstractEventLoop,
|
|
48
|
+
local_cpu_backend: LocalCPUBackend,
|
|
49
|
+
num_threads: int = 8,
|
|
50
|
+
username: str = "",
|
|
51
|
+
password: str = "",
|
|
52
|
+
):
|
|
53
|
+
# this gives us access to self.full_chunk_size_bytes
|
|
54
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
55
|
+
|
|
56
|
+
self.host = host
|
|
57
|
+
self.port = port
|
|
58
|
+
self.num_threads = num_threads
|
|
59
|
+
self.loop = loop
|
|
60
|
+
self.local_cpu_backend = local_cpu_backend
|
|
61
|
+
|
|
62
|
+
self.client = RESPClient(host, port, num_threads, loop, username, password)
|
|
63
|
+
self.pq_executor = AsyncPQExecutor(loop)
|
|
64
|
+
|
|
65
|
+
async def _exists(self, key: CacheEngineKey) -> bool:
|
|
66
|
+
return await self.client.exists(key.to_string())
|
|
67
|
+
|
|
68
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
69
|
+
return await self.pq_executor.submit_job(
|
|
70
|
+
self._exists, key=key, priority=Priorities.PEEK
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
74
|
+
return self.client.exists_sync(key.to_string())
|
|
75
|
+
|
|
76
|
+
async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
77
|
+
key_str = key.to_string()
|
|
78
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
79
|
+
self.meta_shapes,
|
|
80
|
+
self.meta_dtypes,
|
|
81
|
+
self.meta_fmt,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# byte array view of tensor
|
|
85
|
+
recv_buf = memory_obj.byte_array
|
|
86
|
+
if not isinstance(recv_buf, memoryview):
|
|
87
|
+
recv_buf = memoryview(recv_buf)
|
|
88
|
+
await self.client.get(key_str, recv_buf)
|
|
89
|
+
return memory_obj
|
|
90
|
+
|
|
91
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
92
|
+
return await self.pq_executor.submit_job(
|
|
93
|
+
self._get, key=key, priority=Priorities.GET
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
97
|
+
key_str = key.to_string()
|
|
98
|
+
send_buf = memory_obj.byte_array
|
|
99
|
+
if not isinstance(send_buf, memoryview):
|
|
100
|
+
send_buf = memoryview(send_buf)
|
|
101
|
+
await self.client.set(key_str, send_buf)
|
|
102
|
+
|
|
103
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
104
|
+
await self.pq_executor.submit_job(
|
|
105
|
+
self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def support_batched_put(self) -> bool:
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
async def _batched_put(
|
|
112
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
113
|
+
):
|
|
114
|
+
key_strs = [key.to_string() for key in keys]
|
|
115
|
+
send_bufs = [
|
|
116
|
+
memory_obj.byte_array
|
|
117
|
+
if isinstance(memory_obj.byte_array, memoryview)
|
|
118
|
+
else memoryview(memory_obj.byte_array)
|
|
119
|
+
for memory_obj in memory_objs
|
|
120
|
+
]
|
|
121
|
+
await self.client.batch_set(key_strs, send_bufs)
|
|
122
|
+
|
|
123
|
+
async def batched_put(
|
|
124
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
125
|
+
):
|
|
126
|
+
await self.pq_executor.submit_job(
|
|
127
|
+
self._batched_put,
|
|
128
|
+
keys=keys,
|
|
129
|
+
memory_objs=memory_objs,
|
|
130
|
+
priority=Priorities.PUT,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def support_batched_get(self) -> bool:
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
async def _batched_get(
|
|
137
|
+
self, keys: List[CacheEngineKey]
|
|
138
|
+
) -> List[Optional[MemoryObj]]:
|
|
139
|
+
key_strs = [key.to_string() for key in keys]
|
|
140
|
+
memory_objs = [
|
|
141
|
+
self.local_cpu_backend.allocate(
|
|
142
|
+
self.meta_shapes,
|
|
143
|
+
self.meta_dtypes,
|
|
144
|
+
self.meta_fmt,
|
|
145
|
+
)
|
|
146
|
+
for _ in keys
|
|
147
|
+
]
|
|
148
|
+
recv_bufs = [
|
|
149
|
+
memory_obj.byte_array
|
|
150
|
+
if isinstance(memory_obj.byte_array, memoryview)
|
|
151
|
+
else memoryview(memory_obj.byte_array)
|
|
152
|
+
for memory_obj in memory_objs
|
|
153
|
+
]
|
|
154
|
+
await self.client.batch_get(key_strs, recv_bufs)
|
|
155
|
+
return memory_objs
|
|
156
|
+
|
|
157
|
+
async def batched_get(
|
|
158
|
+
self, keys: List[CacheEngineKey]
|
|
159
|
+
) -> List[Optional[MemoryObj]]:
|
|
160
|
+
return await self.pq_executor.submit_job(
|
|
161
|
+
self._batched_get, keys=keys, priority=Priorities.GET
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def support_batched_contains(self) -> bool:
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def batched_contains(self, keys: List[CacheEngineKey]) -> int:
|
|
168
|
+
"""Synchronous batched contains - checks consecutive prefix existence."""
|
|
169
|
+
key_strs = [key.to_string() for key in keys]
|
|
170
|
+
results = self.client.batch_exists_sync(key_strs)
|
|
171
|
+
count = 0
|
|
172
|
+
# we only want the prefixes
|
|
173
|
+
for result in results:
|
|
174
|
+
if not result:
|
|
175
|
+
return count
|
|
176
|
+
count += 1
|
|
177
|
+
return count
|
|
178
|
+
|
|
179
|
+
async def _batched_async_contains(self, keys: List[CacheEngineKey]) -> int:
|
|
180
|
+
key_strs = [key.to_string() for key in keys]
|
|
181
|
+
results = await self.client.batch_exists(key_strs)
|
|
182
|
+
count = 0
|
|
183
|
+
# we only want the prefixes
|
|
184
|
+
for result in results:
|
|
185
|
+
if not result:
|
|
186
|
+
return count
|
|
187
|
+
count += 1
|
|
188
|
+
return count
|
|
189
|
+
|
|
190
|
+
def support_batched_async_contains(self) -> bool:
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
async def batched_async_contains(
|
|
194
|
+
self,
|
|
195
|
+
lookup_id: str,
|
|
196
|
+
keys: List[CacheEngineKey],
|
|
197
|
+
pin: bool = False,
|
|
198
|
+
) -> int:
|
|
199
|
+
"""Check how many keys exist in file system in batch
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
lookup_id: Identifier for this lookup operation
|
|
203
|
+
keys: List of keys to check
|
|
204
|
+
pin: Whether to pin the keys (not used in FS connector)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Number of consecutive keys that exist, starting from the first key
|
|
208
|
+
"""
|
|
209
|
+
# prefetch priority
|
|
210
|
+
return await self.pq_executor.submit_job(
|
|
211
|
+
self._batched_async_contains, keys=keys, priority=Priorities.PREFETCH
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def support_batched_get_non_blocking(self) -> bool:
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
async def batched_get_non_blocking(
|
|
218
|
+
self,
|
|
219
|
+
lookup_id: str,
|
|
220
|
+
keys: List[CacheEngineKey],
|
|
221
|
+
) -> List[MemoryObj]:
|
|
222
|
+
# prefetch priority
|
|
223
|
+
return await self.pq_executor.submit_job(
|
|
224
|
+
self._batched_get, keys=keys, priority=Priorities.PREFETCH
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# TODO
|
|
228
|
+
@no_type_check
|
|
229
|
+
async def list(self) -> List[str]:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
async def close(self):
|
|
233
|
+
await self.pq_executor.shutdown(wait=True)
|
|
234
|
+
self.client.close()
|
|
235
|
+
logger.info("Closed the RESP connection")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class RedisConnector(RemoteConnector):
|
|
239
|
+
"""
|
|
240
|
+
The remote url should start with "redis://" and only have one host-port pair
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def __init__(
|
|
244
|
+
self,
|
|
245
|
+
url: str,
|
|
246
|
+
loop: asyncio.AbstractEventLoop,
|
|
247
|
+
local_cpu_backend: LocalCPUBackend,
|
|
248
|
+
):
|
|
249
|
+
# initialize base class, which includes some common attributes
|
|
250
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
251
|
+
|
|
252
|
+
# set a large max
|
|
253
|
+
self.max_connections = 150
|
|
254
|
+
# redis will crash if we have more than max_connections connections
|
|
255
|
+
self.sem = asyncio.Semaphore(self.max_connections)
|
|
256
|
+
self.pool = redis.ConnectionPool.from_url(
|
|
257
|
+
url, max_connections=self.max_connections
|
|
258
|
+
)
|
|
259
|
+
self.connection = redis.Redis.from_pool(self.pool)
|
|
260
|
+
self.loop = loop
|
|
261
|
+
self.local_cpu_backend = local_cpu_backend
|
|
262
|
+
|
|
263
|
+
self.pq_executor = AsyncPQExecutor(loop)
|
|
264
|
+
|
|
265
|
+
async def _exists(self, key: CacheEngineKey) -> bool:
|
|
266
|
+
async with self.sem:
|
|
267
|
+
return bool(await self.connection.exists(key.to_string() + "metadata"))
|
|
268
|
+
|
|
269
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
270
|
+
return await self.pq_executor.submit_job(
|
|
271
|
+
self._exists, key=key, priority=Priorities.PEEK
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
275
|
+
future = asyncio.run_coroutine_threadsafe(self.exists(key), self.loop)
|
|
276
|
+
return bool(future.result())
|
|
277
|
+
|
|
278
|
+
async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
279
|
+
key_str = key.to_string()
|
|
280
|
+
async with self.sem:
|
|
281
|
+
metadata_bytes = await self.connection.get(key_str + "metadata")
|
|
282
|
+
|
|
283
|
+
if metadata_bytes is None:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
assert not inspect.isawaitable(metadata_bytes)
|
|
287
|
+
|
|
288
|
+
metadata = RemoteMetadata.deserialize(memoryview(metadata_bytes))
|
|
289
|
+
|
|
290
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
291
|
+
metadata.shapes,
|
|
292
|
+
metadata.dtypes,
|
|
293
|
+
metadata.fmt,
|
|
294
|
+
)
|
|
295
|
+
if memory_obj is None:
|
|
296
|
+
logger.warning("Failed to allocate memory during remote receive")
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
# TODO(Jiayi): Find a way to do `get` inplace
|
|
300
|
+
kv_bytes = await self.connection.get(key_str + "kv_bytes")
|
|
301
|
+
assert not inspect.isawaitable(kv_bytes)
|
|
302
|
+
|
|
303
|
+
if kv_bytes is None:
|
|
304
|
+
# TODO (Jiayi): We might need a way to better handle
|
|
305
|
+
# consistency issues.
|
|
306
|
+
# TODO (Jiayi): A better way is to aggregate metadata
|
|
307
|
+
# and kv cache in one key.
|
|
308
|
+
logger.warning(
|
|
309
|
+
"Key exists but KV cache does not exist."
|
|
310
|
+
"Might happen when the cache is evicted by redis."
|
|
311
|
+
)
|
|
312
|
+
async with self.sem:
|
|
313
|
+
await self.connection.delete(key_str + "metadata")
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
if isinstance(memory_obj.byte_array, memoryview):
|
|
317
|
+
view = memory_obj.byte_array
|
|
318
|
+
if view.format == "<B":
|
|
319
|
+
view = view.cast("B")
|
|
320
|
+
else:
|
|
321
|
+
view = memoryview(memory_obj.byte_array)
|
|
322
|
+
|
|
323
|
+
if isinstance(kv_bytes, (bytes, bytearray)):
|
|
324
|
+
view[: metadata.length] = kv_bytes
|
|
325
|
+
elif isinstance(kv_bytes, str):
|
|
326
|
+
converted = kv_bytes.encode("utf-8")
|
|
327
|
+
view[: metadata.length] = converted
|
|
328
|
+
else:
|
|
329
|
+
converted = bytes(kv_bytes)
|
|
330
|
+
view[: metadata.length] = converted
|
|
331
|
+
|
|
332
|
+
return memory_obj
|
|
333
|
+
|
|
334
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
335
|
+
return await self.pq_executor.submit_job(
|
|
336
|
+
self._get, key=key, priority=Priorities.GET
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def support_batched_put(self) -> bool:
|
|
340
|
+
return True
|
|
341
|
+
|
|
342
|
+
async def _batched_put(
|
|
343
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
344
|
+
):
|
|
345
|
+
# calling self.put will create a circular dependency
|
|
346
|
+
await asyncio.gather(
|
|
347
|
+
*(
|
|
348
|
+
self._put(key, memory_obj)
|
|
349
|
+
for key, memory_obj in zip(keys, memory_objs, strict=False)
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
async def batched_put(
|
|
354
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
355
|
+
):
|
|
356
|
+
await self.pq_executor.submit_job(
|
|
357
|
+
self._batched_put,
|
|
358
|
+
keys=keys,
|
|
359
|
+
memory_objs=memory_objs,
|
|
360
|
+
priority=Priorities.PUT,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
364
|
+
# TODO(Jiayi): The following code is ugly.
|
|
365
|
+
# Please use a function like `memory_obj.to_meta()`.
|
|
366
|
+
kv_bytes = memory_obj.byte_array
|
|
367
|
+
kv_shapes = memory_obj.get_shapes()
|
|
368
|
+
kv_dtypes = memory_obj.get_dtypes()
|
|
369
|
+
memory_format = memory_obj.get_memory_format()
|
|
370
|
+
|
|
371
|
+
metadata_bytes = RemoteMetadata(
|
|
372
|
+
len(kv_bytes), kv_shapes, kv_dtypes, memory_format
|
|
373
|
+
).serialize()
|
|
374
|
+
|
|
375
|
+
key_str = key.to_string()
|
|
376
|
+
# kv bytes needs to be set first to avoid race condition
|
|
377
|
+
async with self.sem:
|
|
378
|
+
await self.connection.set(key_str + "kv_bytes", kv_bytes)
|
|
379
|
+
await self.connection.set(key_str + "metadata", metadata_bytes)
|
|
380
|
+
|
|
381
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
382
|
+
await self.pq_executor.submit_job(
|
|
383
|
+
self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# TODO
|
|
387
|
+
@no_type_check
|
|
388
|
+
async def list(self) -> List[str]:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
async def close(self):
|
|
392
|
+
await self.pq_executor.shutdown(wait=True)
|
|
393
|
+
await self.connection.close()
|
|
394
|
+
logger.info("Closed the redis connection")
|
|
395
|
+
|
|
396
|
+
def support_batched_async_contains(self) -> bool:
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
async def _batched_async_contains(
|
|
400
|
+
self,
|
|
401
|
+
lookup_id: str,
|
|
402
|
+
keys: List[CacheEngineKey],
|
|
403
|
+
pin: bool = False,
|
|
404
|
+
) -> int:
|
|
405
|
+
num_hit_counts = 0
|
|
406
|
+
for key in keys:
|
|
407
|
+
async with self.sem:
|
|
408
|
+
if not await self.connection.exists(key.to_string() + "metadata"):
|
|
409
|
+
return num_hit_counts
|
|
410
|
+
num_hit_counts += 1
|
|
411
|
+
return num_hit_counts
|
|
412
|
+
|
|
413
|
+
async def batched_async_contains(
|
|
414
|
+
self,
|
|
415
|
+
lookup_id: str,
|
|
416
|
+
keys: List[CacheEngineKey],
|
|
417
|
+
pin: bool = False,
|
|
418
|
+
) -> int:
|
|
419
|
+
return await self.pq_executor.submit_job(
|
|
420
|
+
self._batched_async_contains,
|
|
421
|
+
lookup_id=lookup_id,
|
|
422
|
+
keys=keys,
|
|
423
|
+
pin=pin,
|
|
424
|
+
priority=Priorities.PEEK,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def support_batched_get_non_blocking(self) -> bool:
|
|
428
|
+
return True
|
|
429
|
+
|
|
430
|
+
async def _batched_get_non_blocking(
|
|
431
|
+
self,
|
|
432
|
+
lookup_id: str,
|
|
433
|
+
keys: List[CacheEngineKey],
|
|
434
|
+
) -> List[MemoryObj]:
|
|
435
|
+
# calling self.get will create a circular dependency
|
|
436
|
+
results = await asyncio.gather(*(self._get(key) for key in keys))
|
|
437
|
+
return [r for r in results if r is not None]
|
|
438
|
+
|
|
439
|
+
async def batched_get_non_blocking(
|
|
440
|
+
self,
|
|
441
|
+
lookup_id: str,
|
|
442
|
+
keys: List[CacheEngineKey],
|
|
443
|
+
) -> List[MemoryObj]:
|
|
444
|
+
return await self.pq_executor.submit_job(
|
|
445
|
+
self._batched_get_non_blocking,
|
|
446
|
+
lookup_id=lookup_id,
|
|
447
|
+
keys=keys,
|
|
448
|
+
priority=Priorities.PREFETCH,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class RedisSentinelConnector(RemoteConnector):
|
|
453
|
+
"""
|
|
454
|
+
Uses redis.Sentinel to connect to a Redis cluster.
|
|
455
|
+
The hosts are specified in the config file, started with "redis-sentinel://"
|
|
456
|
+
and separated by commas.
|
|
457
|
+
|
|
458
|
+
Example:
|
|
459
|
+
remote_url: "redis-sentinel://localhost:26379,localhost:26380,localhost:26381"
|
|
460
|
+
|
|
461
|
+
Extra environment variables:
|
|
462
|
+
- REDIS_SERVICE_NAME (required) -- service name for redis.
|
|
463
|
+
- REDIS_TIMEOUT (optional) -- Timeout in seconds, default is 1 if not set
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
ENV_REDIS_TIMEOUT = "REDIS_TIMEOUT"
|
|
467
|
+
ENV_REDIS_SERVICE_NAME = "REDIS_SERVICE_NAME"
|
|
468
|
+
|
|
469
|
+
def __init__(
|
|
470
|
+
self,
|
|
471
|
+
hosts_and_ports: List[Tuple[str, int]],
|
|
472
|
+
username: str,
|
|
473
|
+
password: str,
|
|
474
|
+
loop: asyncio.AbstractEventLoop,
|
|
475
|
+
local_cpu_backend: LocalCPUBackend,
|
|
476
|
+
):
|
|
477
|
+
# initialize base class, which includes some common attributes
|
|
478
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
479
|
+
|
|
480
|
+
# Get service name
|
|
481
|
+
match os.environ.get(self.ENV_REDIS_SERVICE_NAME):
|
|
482
|
+
case None:
|
|
483
|
+
logger.warning(
|
|
484
|
+
f"Environment variable {self.ENV_REDIS_SERVICE_NAME} is "
|
|
485
|
+
f"not found, using default value 'redismaster'"
|
|
486
|
+
)
|
|
487
|
+
service_name = "redismaster"
|
|
488
|
+
case value:
|
|
489
|
+
service_name = value
|
|
490
|
+
|
|
491
|
+
timeout: float = -1000.0
|
|
492
|
+
|
|
493
|
+
# Get timeout
|
|
494
|
+
match os.environ.get(self.ENV_REDIS_TIMEOUT):
|
|
495
|
+
case None:
|
|
496
|
+
timeout = 1
|
|
497
|
+
case value:
|
|
498
|
+
timeout = float(value)
|
|
499
|
+
|
|
500
|
+
logger.info(f"Host and ports: {hosts_and_ports}")
|
|
501
|
+
self.sentinel = redis.Sentinel(hosts_and_ports, socket_timeout=timeout)
|
|
502
|
+
self.master = self.sentinel.master_for(
|
|
503
|
+
service_name, socket_timeout=timeout, username=username, password=password
|
|
504
|
+
)
|
|
505
|
+
self.slave = self.sentinel.slave_for(
|
|
506
|
+
service_name, socket_timeout=timeout, username=username, password=password
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
self.local_cpu_backend = local_cpu_backend
|
|
510
|
+
|
|
511
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
512
|
+
return bool(self.slave.exists(key.to_string() + "metadata"))
|
|
513
|
+
|
|
514
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
515
|
+
return bool(self.slave.exists(key.to_string() + "metadata"))
|
|
516
|
+
|
|
517
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
518
|
+
key_str = key.to_string()
|
|
519
|
+
metadata_bytes = self.slave.get(key_str + "metadata")
|
|
520
|
+
|
|
521
|
+
if metadata_bytes is None:
|
|
522
|
+
return None
|
|
523
|
+
|
|
524
|
+
assert not inspect.isawaitable(metadata_bytes)
|
|
525
|
+
|
|
526
|
+
metadata = RemoteMetadata.deserialize(metadata_bytes)
|
|
527
|
+
|
|
528
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
529
|
+
metadata.shapes,
|
|
530
|
+
metadata.dtypes,
|
|
531
|
+
metadata.fmt,
|
|
532
|
+
)
|
|
533
|
+
if memory_obj is None:
|
|
534
|
+
logger.warning("Failed to allocate memory during remote receive")
|
|
535
|
+
return None
|
|
536
|
+
|
|
537
|
+
# TODO(Jiayi): Find a way to do `get` inplace
|
|
538
|
+
kv_bytes = self.slave.get(key_str + "kv_bytes")
|
|
539
|
+
|
|
540
|
+
assert not inspect.isawaitable(kv_bytes)
|
|
541
|
+
|
|
542
|
+
if kv_bytes is None:
|
|
543
|
+
# TODO (Jiayi): We might need a way to better handle
|
|
544
|
+
# consistency issues.
|
|
545
|
+
# TODO (Jiayi): A background sweeper might be better
|
|
546
|
+
# for the sake of performance.
|
|
547
|
+
logger.warning(
|
|
548
|
+
"Key exists but KV cache does not exist."
|
|
549
|
+
"Might happen when the cache is evicted by redis."
|
|
550
|
+
)
|
|
551
|
+
self.master.delete(key_str + "metadata")
|
|
552
|
+
return None
|
|
553
|
+
|
|
554
|
+
if isinstance(memory_obj.byte_array, memoryview):
|
|
555
|
+
view = memory_obj.byte_array
|
|
556
|
+
if view.format == "<B":
|
|
557
|
+
view = view.cast("B")
|
|
558
|
+
else:
|
|
559
|
+
view = memoryview(memory_obj.byte_array)
|
|
560
|
+
|
|
561
|
+
if isinstance(kv_bytes, (bytes, bytearray)):
|
|
562
|
+
view[0 : metadata.length] = kv_bytes
|
|
563
|
+
elif isinstance(kv_bytes, str):
|
|
564
|
+
converted = kv_bytes.encode("utf-8")
|
|
565
|
+
view[0 : metadata.length] = converted
|
|
566
|
+
else:
|
|
567
|
+
converted = bytes(kv_bytes)
|
|
568
|
+
view[0 : metadata.length] = converted
|
|
569
|
+
|
|
570
|
+
return memory_obj
|
|
571
|
+
|
|
572
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
573
|
+
# TODO(Jiayi): The following code is ugly.
|
|
574
|
+
# Please use a function like `memory_obj.to_meta()`.
|
|
575
|
+
kv_bytes = memory_obj.byte_array
|
|
576
|
+
kv_shapes = memory_obj.get_shapes()
|
|
577
|
+
kv_dtypes = memory_obj.get_dtypes()
|
|
578
|
+
memory_format = memory_obj.get_memory_format()
|
|
579
|
+
|
|
580
|
+
metadata_bytes = RemoteMetadata(
|
|
581
|
+
len(kv_bytes), kv_shapes, kv_dtypes, memory_format
|
|
582
|
+
).serialize()
|
|
583
|
+
|
|
584
|
+
key_str = key.to_string()
|
|
585
|
+
# kv bytes needs to be set first to avoid race condition
|
|
586
|
+
self.master.set(key_str + "kv_bytes", kv_bytes)
|
|
587
|
+
self.master.set(key_str + "metadata", metadata_bytes)
|
|
588
|
+
|
|
589
|
+
# TODO
|
|
590
|
+
@no_type_check
|
|
591
|
+
async def list(self) -> List[str]:
|
|
592
|
+
pass
|
|
593
|
+
|
|
594
|
+
async def close(self):
|
|
595
|
+
self.master.close()
|
|
596
|
+
self.slave.close()
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
class RedisClusterConnector(RemoteConnector):
|
|
600
|
+
"""
|
|
601
|
+
The remote url starts with "redis-cluster:// and can include one or
|
|
602
|
+
multiple hosts:ports, separated by commas.
|
|
603
|
+
|
|
604
|
+
Example:
|
|
605
|
+
remote_url: "redis-cluster://host1:7000,host2:7000,host3:7000"
|
|
606
|
+
|
|
607
|
+
Extra environment variables:
|
|
608
|
+
- REDIS_TIMEOUT (optional) -- Timeout in seconds, default is 1 if not set
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
def __init__(
|
|
612
|
+
self,
|
|
613
|
+
hosts_and_ports: List[Tuple[str, int]],
|
|
614
|
+
username: str,
|
|
615
|
+
password: str,
|
|
616
|
+
loop: asyncio.AbstractEventLoop,
|
|
617
|
+
local_cpu_backend: LocalCPUBackend,
|
|
618
|
+
):
|
|
619
|
+
# initialize base class, which includes some common attributes
|
|
620
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
621
|
+
|
|
622
|
+
# Convert hosts_and_ports to startup_nodes format expected by RedisCluster
|
|
623
|
+
startup_nodes = [ClusterNode(h, p) for (h, p) in hosts_and_ports]
|
|
624
|
+
|
|
625
|
+
# set a large max
|
|
626
|
+
self.max_connections = 150
|
|
627
|
+
# redis will crash if we have more than max_connections connections
|
|
628
|
+
self.sem = asyncio.Semaphore(self.max_connections)
|
|
629
|
+
|
|
630
|
+
# Initialize cluster connection
|
|
631
|
+
self.cluster = RedisCluster(
|
|
632
|
+
startup_nodes=startup_nodes,
|
|
633
|
+
username=username,
|
|
634
|
+
password=password,
|
|
635
|
+
max_connections=self.max_connections,
|
|
636
|
+
decode_responses=False,
|
|
637
|
+
)
|
|
638
|
+
self.loop = loop
|
|
639
|
+
self.local_cpu_backend = local_cpu_backend
|
|
640
|
+
|
|
641
|
+
self.pq_executor = AsyncPQExecutor(loop)
|
|
642
|
+
|
|
643
|
+
async def _exists(self, key: CacheEngineKey) -> bool:
|
|
644
|
+
async with self.sem:
|
|
645
|
+
return bool(await self.cluster.exists(key.to_string() + "metadata"))
|
|
646
|
+
|
|
647
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
648
|
+
return await self.pq_executor.submit_job(
|
|
649
|
+
self._exists, key=key, priority=Priorities.PEEK
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
653
|
+
future = asyncio.run_coroutine_threadsafe(self.exists(key), self.loop)
|
|
654
|
+
return bool(future.result())
|
|
655
|
+
|
|
656
|
+
async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
657
|
+
key_str = key.to_string()
|
|
658
|
+
async with self.sem:
|
|
659
|
+
metadata_bytes = await self.cluster.get(key_str + "metadata")
|
|
660
|
+
|
|
661
|
+
if metadata_bytes is None:
|
|
662
|
+
return None
|
|
663
|
+
|
|
664
|
+
assert not inspect.isawaitable(metadata_bytes)
|
|
665
|
+
|
|
666
|
+
metadata = RemoteMetadata.deserialize(memoryview(metadata_bytes))
|
|
667
|
+
|
|
668
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
669
|
+
metadata.shapes,
|
|
670
|
+
metadata.dtypes,
|
|
671
|
+
metadata.fmt,
|
|
672
|
+
)
|
|
673
|
+
if memory_obj is None:
|
|
674
|
+
logger.warning("Failed to allocate memory during remote receive")
|
|
675
|
+
return None
|
|
676
|
+
|
|
677
|
+
# TODO(Jiayi): Find a way to do `get` inplace
|
|
678
|
+
kv_bytes = await self.cluster.get(key_str + "kv_bytes")
|
|
679
|
+
|
|
680
|
+
assert not inspect.isawaitable(kv_bytes)
|
|
681
|
+
|
|
682
|
+
if kv_bytes is None:
|
|
683
|
+
# TODO (Jiayi): We might need a way to better handle
|
|
684
|
+
# consistency issues.
|
|
685
|
+
# TODO (Jiayi): A better way is to aggregate metadata
|
|
686
|
+
# and kv cache in one key.
|
|
687
|
+
logger.warning(
|
|
688
|
+
"Key exists but KV cache does not exist."
|
|
689
|
+
"Might happen when the cache is evicted by redis."
|
|
690
|
+
)
|
|
691
|
+
async with self.sem:
|
|
692
|
+
await self.cluster.delete(key_str + "metadata")
|
|
693
|
+
return None
|
|
694
|
+
|
|
695
|
+
if isinstance(memory_obj.byte_array, memoryview):
|
|
696
|
+
view = memory_obj.byte_array
|
|
697
|
+
if view.format == "<B":
|
|
698
|
+
view = view.cast("B")
|
|
699
|
+
else:
|
|
700
|
+
view = memoryview(memory_obj.byte_array)
|
|
701
|
+
|
|
702
|
+
if isinstance(kv_bytes, (bytes, bytearray)):
|
|
703
|
+
view[: metadata.length] = kv_bytes
|
|
704
|
+
elif isinstance(kv_bytes, str):
|
|
705
|
+
converted = kv_bytes.encode("utf-8")
|
|
706
|
+
view[: metadata.length] = converted
|
|
707
|
+
else:
|
|
708
|
+
converted = bytes(kv_bytes)
|
|
709
|
+
view[: metadata.length] = converted
|
|
710
|
+
|
|
711
|
+
return memory_obj
|
|
712
|
+
|
|
713
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
714
|
+
return await self.pq_executor.submit_job(
|
|
715
|
+
self._get, key=key, priority=Priorities.GET
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def support_batched_put(self) -> bool:
|
|
719
|
+
return True
|
|
720
|
+
|
|
721
|
+
async def _batched_put(
|
|
722
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
723
|
+
):
|
|
724
|
+
# calling self.put will create a circular dependency
|
|
725
|
+
await asyncio.gather(
|
|
726
|
+
*(
|
|
727
|
+
self._put(key, memory_obj)
|
|
728
|
+
for key, memory_obj in zip(keys, memory_objs, strict=False)
|
|
729
|
+
)
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
async def batched_put(
|
|
733
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
734
|
+
):
|
|
735
|
+
await self.pq_executor.submit_job(
|
|
736
|
+
self._batched_put,
|
|
737
|
+
keys=keys,
|
|
738
|
+
memory_objs=memory_objs,
|
|
739
|
+
priority=Priorities.PUT,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
743
|
+
# TODO(Jiayi): The following code is ugly.
|
|
744
|
+
# Please use a function like `memory_obj.to_meta()`.
|
|
745
|
+
kv_bytes = memory_obj.byte_array
|
|
746
|
+
kv_shapes = memory_obj.get_shapes()
|
|
747
|
+
kv_dtypes = memory_obj.get_dtypes()
|
|
748
|
+
memory_format = memory_obj.get_memory_format()
|
|
749
|
+
|
|
750
|
+
metadata_bytes = RemoteMetadata(
|
|
751
|
+
len(kv_bytes), kv_shapes, kv_dtypes, memory_format
|
|
752
|
+
).serialize()
|
|
753
|
+
|
|
754
|
+
key_str = key.to_string()
|
|
755
|
+
# kv bytes needs to be set first to avoid race condition
|
|
756
|
+
async with self.sem:
|
|
757
|
+
await self.cluster.set(key_str + "kv_bytes", kv_bytes)
|
|
758
|
+
await self.cluster.set(key_str + "metadata", metadata_bytes)
|
|
759
|
+
|
|
760
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
761
|
+
await self.pq_executor.submit_job(
|
|
762
|
+
self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
# TODO
|
|
766
|
+
@no_type_check
|
|
767
|
+
async def list(self) -> List[str]:
|
|
768
|
+
pass
|
|
769
|
+
|
|
770
|
+
async def close(self):
|
|
771
|
+
await self.pq_executor.shutdown(wait=True)
|
|
772
|
+
await self.cluster.close()
|
|
773
|
+
logger.info("Closed the redis cluster connection")
|
|
774
|
+
|
|
775
|
+
def support_batched_async_contains(self) -> bool:
|
|
776
|
+
return True
|
|
777
|
+
|
|
778
|
+
async def _batched_async_contains(
|
|
779
|
+
self,
|
|
780
|
+
lookup_id: str,
|
|
781
|
+
keys: List[CacheEngineKey],
|
|
782
|
+
pin: bool = False,
|
|
783
|
+
) -> int:
|
|
784
|
+
num_hit_counts = 0
|
|
785
|
+
for key in keys:
|
|
786
|
+
async with self.sem:
|
|
787
|
+
if not await self.cluster.exists(key.to_string() + "metadata"):
|
|
788
|
+
return num_hit_counts
|
|
789
|
+
num_hit_counts += 1
|
|
790
|
+
return num_hit_counts
|
|
791
|
+
|
|
792
|
+
async def batched_async_contains(
|
|
793
|
+
self,
|
|
794
|
+
lookup_id: str,
|
|
795
|
+
keys: List[CacheEngineKey],
|
|
796
|
+
pin: bool = False,
|
|
797
|
+
) -> int:
|
|
798
|
+
return await self.pq_executor.submit_job(
|
|
799
|
+
self._batched_async_contains,
|
|
800
|
+
lookup_id=lookup_id,
|
|
801
|
+
keys=keys,
|
|
802
|
+
pin=pin,
|
|
803
|
+
priority=Priorities.PEEK,
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
def support_batched_get_non_blocking(self) -> bool:
|
|
807
|
+
return True
|
|
808
|
+
|
|
809
|
+
async def _batched_get_non_blocking(
|
|
810
|
+
self,
|
|
811
|
+
lookup_id: str,
|
|
812
|
+
keys: List[CacheEngineKey],
|
|
813
|
+
) -> List[MemoryObj]:
|
|
814
|
+
# calling self.get will create a circular dependency
|
|
815
|
+
results = await asyncio.gather(*(self._get(key) for key in keys))
|
|
816
|
+
return [r for r in results if r is not None]
|
|
817
|
+
|
|
818
|
+
async def batched_get_non_blocking(
|
|
819
|
+
self,
|
|
820
|
+
lookup_id: str,
|
|
821
|
+
keys: List[CacheEngineKey],
|
|
822
|
+
) -> List[MemoryObj]:
|
|
823
|
+
return await self.pq_executor.submit_job(
|
|
824
|
+
self._batched_get_non_blocking,
|
|
825
|
+
lookup_id=lookup_id,
|
|
826
|
+
keys=keys,
|
|
827
|
+
priority=Priorities.PREFETCH,
|
|
828
|
+
)
|