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,627 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
ValkeyConnector — high-throughput Valkey connector using the GLIDE sync
|
|
4
|
+
client (standalone or cluster) with a ThreadPoolExecutor and per-thread clients.
|
|
5
|
+
|
|
6
|
+
This replaces the legacy async ValkeyConnector / ValkeyClusterConnector
|
|
7
|
+
that used the async GLIDE client with 2-key storage. The implementation
|
|
8
|
+
is shared with ``sync_valkey_connector.SyncValkeyConnector`` (which
|
|
9
|
+
registers on the ``valkey-sync://`` scheme as a backward-compat alias).
|
|
10
|
+
|
|
11
|
+
Design choices:
|
|
12
|
+
- N worker threads, each with its own GLIDE sync client
|
|
13
|
+
(``GlideClient`` in standalone mode, ``GlideClusterClient`` in cluster mode)
|
|
14
|
+
via ``threading.local()``, enabling true parallel I/O when the GIL is
|
|
15
|
+
released during FFI calls.
|
|
16
|
+
- Direct ``memoryview`` access to pinned CPU memory — no shared-memory
|
|
17
|
+
arena or cross-process copies needed since threads share the parent's
|
|
18
|
+
address space.
|
|
19
|
+
- Single-key storage (like RESPConnector) to halve Valkey round-trips
|
|
20
|
+
compared to the legacy 2-key metadata/kv_bytes split.
|
|
21
|
+
- Priority scheduling via ``AsyncPQExecutor`` (PEEK > PREFETCH > GET > PUT)
|
|
22
|
+
ensures latency-sensitive lookups are not delayed behind bulk writes,
|
|
23
|
+
matching the priority scheme used by ``RESPConnector``.
|
|
24
|
+
|
|
25
|
+
Migration notes from the old ValkeyConnector:
|
|
26
|
+
- Standalone mode (default) uses ``GlideClient`` and supports ``database_id``.
|
|
27
|
+
- Cluster mode (``valkey_mode: "cluster"``) uses ``GlideClusterClient`` which
|
|
28
|
+
auto-discovers cluster topology from a single seed node.
|
|
29
|
+
|
|
30
|
+
Requires ``valkey-glide`` with PRs #5492 (zero-copy SET) and #5493 (buffer GET).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# Standard
|
|
34
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
35
|
+
from enum import IntEnum, auto
|
|
36
|
+
from typing import List, Optional
|
|
37
|
+
import asyncio
|
|
38
|
+
import inspect
|
|
39
|
+
import threading
|
|
40
|
+
|
|
41
|
+
# First Party
|
|
42
|
+
from lmcache.logging import init_logger
|
|
43
|
+
from lmcache.utils import CacheEngineKey
|
|
44
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
45
|
+
from lmcache.v1.storage_backend.connector.base_connector import RemoteConnector
|
|
46
|
+
from lmcache.v1.storage_backend.job_executor.pq_executor import AsyncPQExecutor
|
|
47
|
+
from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
|
|
48
|
+
|
|
49
|
+
logger = init_logger(__name__)
|
|
50
|
+
|
|
51
|
+
#: Default request timeout (seconds).
|
|
52
|
+
DEFAULT_REQUEST_TIMEOUT_SECS: float = 5.0
|
|
53
|
+
#: Default connection timeout (seconds).
|
|
54
|
+
DEFAULT_CONNECTION_TIMEOUT_SECS: float = 10.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Priorities(IntEnum):
|
|
58
|
+
"""Operation priorities for the ``AsyncPQExecutor``.
|
|
59
|
+
|
|
60
|
+
Lower numeric value = higher priority. Matches the scheme used by
|
|
61
|
+
``RESPConnector`` so that exists/peek checks run before bulk writes.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
PEEK = auto()
|
|
65
|
+
PREFETCH = auto()
|
|
66
|
+
GET = auto()
|
|
67
|
+
PUT = auto()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class _ThreadWorkerPool:
|
|
71
|
+
"""Manages a pool of threads, each with its own GLIDE sync client.
|
|
72
|
+
|
|
73
|
+
Each thread gets an independent GLIDE sync client
|
|
74
|
+
(``GlideClient`` or ``GlideClusterClient``) via
|
|
75
|
+
``threading.local()``, enabling true parallel I/O when the GIL is
|
|
76
|
+
released during FFI calls.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
host: Valkey server hostname.
|
|
80
|
+
port: Valkey server port.
|
|
81
|
+
num_workers: Number of worker threads.
|
|
82
|
+
username: Valkey authentication username.
|
|
83
|
+
password: Valkey authentication password.
|
|
84
|
+
request_timeout: Timeout in seconds for GLIDE requests and
|
|
85
|
+
Future.result() calls.
|
|
86
|
+
connection_timeout: Timeout in seconds for initial GLIDE client
|
|
87
|
+
connections and thread pool warmup.
|
|
88
|
+
tls_enable: Whether to use TLS for Valkey connections.
|
|
89
|
+
cluster_mode: If True, use GlideClusterClient; else GlideClient.
|
|
90
|
+
database_id: Database ID for standalone mode (ignored in cluster).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
host: str,
|
|
96
|
+
port: int,
|
|
97
|
+
num_workers: int,
|
|
98
|
+
username: str,
|
|
99
|
+
password: str,
|
|
100
|
+
request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECS,
|
|
101
|
+
connection_timeout: float = DEFAULT_CONNECTION_TIMEOUT_SECS,
|
|
102
|
+
tls_enable: bool = False,
|
|
103
|
+
cluster_mode: bool = False,
|
|
104
|
+
database_id: Optional[int] = None,
|
|
105
|
+
):
|
|
106
|
+
self.num_workers = num_workers
|
|
107
|
+
self._host = host
|
|
108
|
+
self._port = port
|
|
109
|
+
self._username = username
|
|
110
|
+
self._password = password
|
|
111
|
+
self._request_timeout = request_timeout
|
|
112
|
+
self._request_timeout_ms = int(request_timeout * 1000)
|
|
113
|
+
self._connection_timeout_ms = int(connection_timeout * 1000)
|
|
114
|
+
self._tls_enable = tls_enable
|
|
115
|
+
self._cluster_mode = cluster_mode
|
|
116
|
+
self._database_id = database_id
|
|
117
|
+
self._local = threading.local()
|
|
118
|
+
self._has_buffer_get: Optional[bool] = None
|
|
119
|
+
|
|
120
|
+
self._executor = ThreadPoolExecutor(
|
|
121
|
+
max_workers=num_workers,
|
|
122
|
+
thread_name_prefix="valkey",
|
|
123
|
+
)
|
|
124
|
+
# Warm up: create a client on each thread
|
|
125
|
+
futs = [self._executor.submit(self._get_client) for _ in range(num_workers)]
|
|
126
|
+
for f in futs:
|
|
127
|
+
f.result(timeout=connection_timeout)
|
|
128
|
+
mode_str = "cluster" if cluster_mode else "standalone"
|
|
129
|
+
logger.info(
|
|
130
|
+
"Valkey thread pool: %d threads, mode=%s, per-thread clients, "
|
|
131
|
+
"buffer_get=%s",
|
|
132
|
+
num_workers,
|
|
133
|
+
mode_str,
|
|
134
|
+
self._has_buffer_get,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _get_client(self): # type: ignore[no-untyped-def]
|
|
138
|
+
"""Get or create the per-thread GLIDE sync client.
|
|
139
|
+
|
|
140
|
+
Creates a ``GlideClusterClient`` (cluster mode) or ``GlideClient``
|
|
141
|
+
(standalone mode) depending on the ``cluster_mode`` flag.
|
|
142
|
+
"""
|
|
143
|
+
# Third Party
|
|
144
|
+
import glide_sync # type: ignore[import-untyped]
|
|
145
|
+
|
|
146
|
+
client = getattr(self._local, "client", None)
|
|
147
|
+
if client is not None:
|
|
148
|
+
return client
|
|
149
|
+
|
|
150
|
+
credentials = None
|
|
151
|
+
if self._username or self._password:
|
|
152
|
+
credentials = glide_sync.ServerCredentials(self._username, self._password)
|
|
153
|
+
|
|
154
|
+
address = glide_sync.NodeAddress(self._host, self._port)
|
|
155
|
+
|
|
156
|
+
if self._cluster_mode:
|
|
157
|
+
advanced = glide_sync.AdvancedGlideClusterClientConfiguration(
|
|
158
|
+
connection_timeout=self._connection_timeout_ms,
|
|
159
|
+
)
|
|
160
|
+
config_kwargs: dict = {
|
|
161
|
+
"addresses": [address],
|
|
162
|
+
"request_timeout": self._request_timeout_ms,
|
|
163
|
+
"use_tls": self._tls_enable,
|
|
164
|
+
"advanced_config": advanced,
|
|
165
|
+
}
|
|
166
|
+
if credentials is not None:
|
|
167
|
+
config_kwargs["credentials"] = credentials
|
|
168
|
+
config = glide_sync.GlideClusterClientConfiguration(**config_kwargs)
|
|
169
|
+
client = glide_sync.GlideClusterClient.create(config)
|
|
170
|
+
else:
|
|
171
|
+
# Standalone mode — supports database_id and advanced config
|
|
172
|
+
advanced = glide_sync.AdvancedGlideClientConfiguration(
|
|
173
|
+
connection_timeout=self._connection_timeout_ms,
|
|
174
|
+
)
|
|
175
|
+
config_kwargs = {
|
|
176
|
+
"addresses": [address],
|
|
177
|
+
"request_timeout": self._request_timeout_ms,
|
|
178
|
+
"use_tls": self._tls_enable,
|
|
179
|
+
"advanced_config": advanced,
|
|
180
|
+
}
|
|
181
|
+
if credentials is not None:
|
|
182
|
+
config_kwargs["credentials"] = credentials
|
|
183
|
+
if self._database_id is not None:
|
|
184
|
+
config_kwargs["database_id"] = self._database_id
|
|
185
|
+
config = glide_sync.GlideClientConfiguration(**config_kwargs)
|
|
186
|
+
client = glide_sync.GlideClient.create(config)
|
|
187
|
+
|
|
188
|
+
self._local.client = client
|
|
189
|
+
|
|
190
|
+
if self._has_buffer_get is None:
|
|
191
|
+
self._has_buffer_get = "buffer" in inspect.signature(client.get).parameters
|
|
192
|
+
|
|
193
|
+
return client
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def has_buffer_get(self) -> bool:
|
|
197
|
+
"""Whether the GLIDE client supports buffer GET."""
|
|
198
|
+
if self._has_buffer_get is None:
|
|
199
|
+
self._executor.submit(self._get_client).result(
|
|
200
|
+
timeout=self._connection_timeout_ms / 1000
|
|
201
|
+
)
|
|
202
|
+
return bool(self._has_buffer_get)
|
|
203
|
+
|
|
204
|
+
def _do_set(self, key_str: str, data: bytes) -> None:
|
|
205
|
+
"""SET a key (runs on a worker thread)."""
|
|
206
|
+
self._get_client().set(key_str.encode(), data)
|
|
207
|
+
|
|
208
|
+
def _do_get_into(self, key_str: str, buf: memoryview) -> bool:
|
|
209
|
+
"""GET a key into a buffer (runs on a worker thread)."""
|
|
210
|
+
client = self._get_client()
|
|
211
|
+
if self._has_buffer_get:
|
|
212
|
+
result = client.get(key_str.encode(), buffer=buf)
|
|
213
|
+
return result is not None
|
|
214
|
+
else:
|
|
215
|
+
data = client.get(key_str.encode())
|
|
216
|
+
if data is None:
|
|
217
|
+
return False
|
|
218
|
+
buf[: len(data)] = data
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
def _do_exists(self, key_str: str) -> bool:
|
|
222
|
+
"""Check if a key exists (runs on a worker thread)."""
|
|
223
|
+
return bool(self._get_client().exists([key_str.encode()]))
|
|
224
|
+
|
|
225
|
+
def submit_set(self, key_str: str, data: bytes) -> Future:
|
|
226
|
+
"""Submit a SET operation."""
|
|
227
|
+
return self._executor.submit(self._do_set, key_str, data)
|
|
228
|
+
|
|
229
|
+
def submit_get_into(self, key_str: str, buf: memoryview) -> Future:
|
|
230
|
+
"""Submit a GET-into-buffer operation."""
|
|
231
|
+
return self._executor.submit(self._do_get_into, key_str, buf)
|
|
232
|
+
|
|
233
|
+
def submit_exists(self, key_str: str) -> Future:
|
|
234
|
+
"""Submit an EXISTS check."""
|
|
235
|
+
return self._executor.submit(self._do_exists, key_str)
|
|
236
|
+
|
|
237
|
+
def _close_client(self) -> None:
|
|
238
|
+
"""Close the per-thread GLIDE client (runs on a worker thread)."""
|
|
239
|
+
client = getattr(self._local, "client", None)
|
|
240
|
+
if client is not None:
|
|
241
|
+
try:
|
|
242
|
+
client.close()
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
logger.debug("Error closing per-thread GLIDE client: %s", exc)
|
|
245
|
+
self._local.client = None
|
|
246
|
+
|
|
247
|
+
def close(self) -> None:
|
|
248
|
+
"""Shut down all per-thread GLIDE clients and the thread pool."""
|
|
249
|
+
close_futs = [
|
|
250
|
+
self._executor.submit(self._close_client) for _ in range(self.num_workers)
|
|
251
|
+
]
|
|
252
|
+
for f in close_futs:
|
|
253
|
+
try:
|
|
254
|
+
f.result(timeout=self._request_timeout)
|
|
255
|
+
except Exception as exc:
|
|
256
|
+
logger.debug("Error during client close: %s", exc)
|
|
257
|
+
self._executor.shutdown(wait=True, cancel_futures=False)
|
|
258
|
+
logger.info("Valkey thread pool closed")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ValkeyConnector(RemoteConnector):
|
|
262
|
+
"""High-throughput Valkey connector using GLIDE sync cluster client with
|
|
263
|
+
per-thread clients, ThreadPoolExecutor, and priority scheduling.
|
|
264
|
+
|
|
265
|
+
Uses N worker threads, each with its own GLIDE sync client, and
|
|
266
|
+
direct memoryview access for zero-copy data transfer. Single-key
|
|
267
|
+
storage halves Valkey round-trips compared to the legacy 2-key split.
|
|
268
|
+
|
|
269
|
+
Operations are dispatched through an ``AsyncPQExecutor`` with priority
|
|
270
|
+
levels (PEEK > PREFETCH > GET > PUT) so that latency-sensitive lookups
|
|
271
|
+
are not delayed behind bulk writes.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
host: Valkey server hostname.
|
|
275
|
+
port: Valkey server port.
|
|
276
|
+
loop: Asyncio event loop (used for PQ executor and wrap_future).
|
|
277
|
+
local_cpu_backend: Backend for allocating CPU memory objects.
|
|
278
|
+
num_workers: Number of worker threads (default 8).
|
|
279
|
+
username: Valkey authentication username.
|
|
280
|
+
password: Valkey authentication password.
|
|
281
|
+
request_timeout: Timeout in seconds for requests and
|
|
282
|
+
Future.result() calls (default 5).
|
|
283
|
+
connection_timeout: Timeout in seconds for initial client
|
|
284
|
+
connections (default 10).
|
|
285
|
+
tls_enable: Whether to use TLS for Valkey connections.
|
|
286
|
+
cluster_mode: If True, use GlideClusterClient; else GlideClient.
|
|
287
|
+
database_id: Database ID for standalone mode (ignored in cluster).
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __init__(
|
|
291
|
+
self,
|
|
292
|
+
host: str,
|
|
293
|
+
port: int,
|
|
294
|
+
loop: asyncio.AbstractEventLoop,
|
|
295
|
+
local_cpu_backend: LocalCPUBackend,
|
|
296
|
+
num_workers: int = 8,
|
|
297
|
+
username: str = "",
|
|
298
|
+
password: str = "",
|
|
299
|
+
request_timeout: float = DEFAULT_REQUEST_TIMEOUT_SECS,
|
|
300
|
+
connection_timeout: float = DEFAULT_CONNECTION_TIMEOUT_SECS,
|
|
301
|
+
tls_enable: bool = False,
|
|
302
|
+
cluster_mode: bool = False,
|
|
303
|
+
database_id: Optional[int] = None,
|
|
304
|
+
):
|
|
305
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
306
|
+
|
|
307
|
+
self.host = host
|
|
308
|
+
self.port = port
|
|
309
|
+
self.num_workers = num_workers
|
|
310
|
+
self._request_timeout = request_timeout
|
|
311
|
+
self.loop = loop
|
|
312
|
+
self.local_cpu_backend = local_cpu_backend
|
|
313
|
+
|
|
314
|
+
self._pool = _ThreadWorkerPool(
|
|
315
|
+
host,
|
|
316
|
+
port,
|
|
317
|
+
num_workers,
|
|
318
|
+
username,
|
|
319
|
+
password,
|
|
320
|
+
request_timeout=request_timeout,
|
|
321
|
+
connection_timeout=connection_timeout,
|
|
322
|
+
tls_enable=tls_enable,
|
|
323
|
+
cluster_mode=cluster_mode,
|
|
324
|
+
database_id=database_id,
|
|
325
|
+
)
|
|
326
|
+
self._pq_executor = AsyncPQExecutor(loop)
|
|
327
|
+
|
|
328
|
+
# ── EXISTS ───────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
async def _exists(self, key: CacheEngineKey) -> bool:
|
|
331
|
+
"""Internal: check if a key exists in Valkey."""
|
|
332
|
+
return await asyncio.wrap_future(self._pool.submit_exists(key.to_string()))
|
|
333
|
+
|
|
334
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
335
|
+
"""Check if a key exists in Valkey."""
|
|
336
|
+
return await self._pq_executor.submit_job(
|
|
337
|
+
self._exists, key=key, priority=Priorities.PEEK
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
341
|
+
"""Synchronously check if a key exists in Valkey."""
|
|
342
|
+
return self._pool.submit_exists(key.to_string()).result(
|
|
343
|
+
timeout=self._request_timeout
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# ── GET ──────────────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
async def _get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
349
|
+
"""Internal: retrieve a memory object from Valkey by key."""
|
|
350
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
351
|
+
self.meta_shapes, self.meta_dtypes, self.meta_fmt
|
|
352
|
+
)
|
|
353
|
+
if memory_obj is None:
|
|
354
|
+
logger.warning("Failed to allocate memory during remote receive")
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
dst = memory_obj.byte_array
|
|
358
|
+
if not isinstance(dst, memoryview):
|
|
359
|
+
dst = memoryview(dst)
|
|
360
|
+
if dst.format != "B":
|
|
361
|
+
dst = dst.cast("B")
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
found = await asyncio.wrap_future(
|
|
365
|
+
self._pool.submit_get_into(key.to_string(), dst)
|
|
366
|
+
)
|
|
367
|
+
except Exception:
|
|
368
|
+
memory_obj.ref_count_down()
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
if not found:
|
|
372
|
+
memory_obj.ref_count_down()
|
|
373
|
+
return None
|
|
374
|
+
return memory_obj
|
|
375
|
+
|
|
376
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
377
|
+
"""Retrieve a memory object from Valkey by key."""
|
|
378
|
+
return await self._pq_executor.submit_job(
|
|
379
|
+
self._get, key=key, priority=Priorities.GET
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# ── PUT ──────────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj) -> None:
|
|
385
|
+
"""Internal: store a memory object in Valkey.
|
|
386
|
+
|
|
387
|
+
Note: This method does NOT call ``ref_count_down()`` on the memory
|
|
388
|
+
object. Reference counting is managed by the caller
|
|
389
|
+
(``RemoteBackend.submit_put_task``), which increments before
|
|
390
|
+
submitting and decrements in the done callback.
|
|
391
|
+
"""
|
|
392
|
+
await asyncio.wrap_future(
|
|
393
|
+
self._pool.submit_set(key.to_string(), memory_obj.byte_array)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj) -> None:
|
|
397
|
+
"""Store a memory object in Valkey."""
|
|
398
|
+
await self._pq_executor.submit_job(
|
|
399
|
+
self._put, key=key, memory_obj=memory_obj, priority=Priorities.PUT
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# ── BATCHED PUT ──────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
def support_batched_put(self) -> bool:
|
|
405
|
+
"""Returns True — batched put is supported."""
|
|
406
|
+
return True
|
|
407
|
+
|
|
408
|
+
async def _batched_put(
|
|
409
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
410
|
+
) -> None:
|
|
411
|
+
"""Internal: store multiple memory objects in Valkey in parallel."""
|
|
412
|
+
n = len(keys)
|
|
413
|
+
key_strs = [k.to_string() for k in keys]
|
|
414
|
+
|
|
415
|
+
futures = [
|
|
416
|
+
self._pool.submit_set(key_strs[i], memory_objs[i].byte_array)
|
|
417
|
+
for i in range(n)
|
|
418
|
+
]
|
|
419
|
+
wrapped = [asyncio.wrap_future(f) for f in futures]
|
|
420
|
+
await asyncio.gather(*wrapped)
|
|
421
|
+
|
|
422
|
+
async def batched_put(
|
|
423
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
424
|
+
) -> None:
|
|
425
|
+
"""Store multiple memory objects in Valkey in parallel."""
|
|
426
|
+
await self._pq_executor.submit_job(
|
|
427
|
+
self._batched_put,
|
|
428
|
+
keys=keys,
|
|
429
|
+
memory_objs=memory_objs,
|
|
430
|
+
priority=Priorities.PUT,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# ── BATCHED GET ──────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
def support_batched_get(self) -> bool:
|
|
436
|
+
"""Returns True — batched get is supported."""
|
|
437
|
+
return True
|
|
438
|
+
|
|
439
|
+
async def _batched_get(
|
|
440
|
+
self, keys: List[CacheEngineKey]
|
|
441
|
+
) -> List[Optional[MemoryObj]]:
|
|
442
|
+
"""Internal: retrieve multiple memory objects from Valkey in parallel.
|
|
443
|
+
|
|
444
|
+
Note: Once an allocation failure occurs, all subsequent slots are
|
|
445
|
+
set to ``None`` (intentional — memory pressure means further
|
|
446
|
+
allocations would also fail).
|
|
447
|
+
"""
|
|
448
|
+
n = len(keys)
|
|
449
|
+
key_strs = [k.to_string() for k in keys]
|
|
450
|
+
|
|
451
|
+
memory_objs: List[Optional[MemoryObj]] = []
|
|
452
|
+
dst_bufs: List[Optional[memoryview]] = []
|
|
453
|
+
alloc_failed = False
|
|
454
|
+
|
|
455
|
+
for _ in keys:
|
|
456
|
+
if alloc_failed:
|
|
457
|
+
memory_objs.append(None)
|
|
458
|
+
dst_bufs.append(None)
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
mobj = self.local_cpu_backend.allocate(
|
|
462
|
+
self.meta_shapes, self.meta_dtypes, self.meta_fmt
|
|
463
|
+
)
|
|
464
|
+
if mobj is None:
|
|
465
|
+
logger.warning(
|
|
466
|
+
"Failed to allocate memory during batched remote receive"
|
|
467
|
+
)
|
|
468
|
+
alloc_failed = True
|
|
469
|
+
memory_objs.append(None)
|
|
470
|
+
dst_bufs.append(None)
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
memory_objs.append(mobj)
|
|
474
|
+
dst = mobj.byte_array
|
|
475
|
+
if not isinstance(dst, memoryview):
|
|
476
|
+
dst = memoryview(dst)
|
|
477
|
+
if dst.format != "B":
|
|
478
|
+
dst = dst.cast("B")
|
|
479
|
+
dst_bufs.append(dst)
|
|
480
|
+
|
|
481
|
+
# Submit GET futures for allocated slots; track which indices have
|
|
482
|
+
# real futures vs None (allocation failed).
|
|
483
|
+
live_indices: List[int] = []
|
|
484
|
+
live_futures: List[asyncio.Future] = []
|
|
485
|
+
for i in range(n):
|
|
486
|
+
if memory_objs[i] is not None and dst_bufs[i] is not None:
|
|
487
|
+
live_indices.append(i)
|
|
488
|
+
live_futures.append(
|
|
489
|
+
asyncio.wrap_future(
|
|
490
|
+
self._pool.submit_get_into(
|
|
491
|
+
key_strs[i],
|
|
492
|
+
dst_bufs[i], # type: ignore[arg-type]
|
|
493
|
+
)
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
try:
|
|
498
|
+
results = await asyncio.gather(*live_futures)
|
|
499
|
+
for idx, found in zip(live_indices, results, strict=True):
|
|
500
|
+
if not found:
|
|
501
|
+
memory_objs[idx].ref_count_down() # type: ignore[union-attr]
|
|
502
|
+
memory_objs[idx] = None
|
|
503
|
+
except Exception:
|
|
504
|
+
for mobj in memory_objs:
|
|
505
|
+
if mobj is not None:
|
|
506
|
+
mobj.ref_count_down()
|
|
507
|
+
raise
|
|
508
|
+
|
|
509
|
+
return memory_objs
|
|
510
|
+
|
|
511
|
+
async def batched_get(
|
|
512
|
+
self, keys: List[CacheEngineKey]
|
|
513
|
+
) -> List[Optional[MemoryObj]]:
|
|
514
|
+
"""Retrieve multiple memory objects from Valkey in parallel."""
|
|
515
|
+
return await self._pq_executor.submit_job(
|
|
516
|
+
self._batched_get, keys=keys, priority=Priorities.GET
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# ── BATCHED CONTAINS ─────────────────────────────────────────────────
|
|
520
|
+
|
|
521
|
+
def support_batched_contains(self) -> bool:
|
|
522
|
+
"""Returns True — synchronous batched contains is supported."""
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
def _count_consecutive_exists(self, keys: List[CacheEngineKey]) -> int:
|
|
526
|
+
"""Check how many consecutive keys exist (prefix match).
|
|
527
|
+
|
|
528
|
+
Fans out individual EXISTS checks across the thread pool for
|
|
529
|
+
parallel round-trips: wall-clock time is roughly
|
|
530
|
+
``ceil(N / num_workers) * RTT`` instead of ``N * RTT``.
|
|
531
|
+
"""
|
|
532
|
+
key_strs = [k.to_string() for k in keys]
|
|
533
|
+
futures = [self._pool.submit_exists(k) for k in key_strs]
|
|
534
|
+
for i, fut in enumerate(futures):
|
|
535
|
+
if not fut.result(timeout=self._request_timeout):
|
|
536
|
+
return i
|
|
537
|
+
return len(futures)
|
|
538
|
+
|
|
539
|
+
def batched_contains(self, keys: List[CacheEngineKey]) -> int:
|
|
540
|
+
"""Synchronously check how many consecutive keys exist."""
|
|
541
|
+
return self._count_consecutive_exists(keys)
|
|
542
|
+
|
|
543
|
+
def support_batched_async_contains(self) -> bool:
|
|
544
|
+
"""Returns True — async batched contains is supported."""
|
|
545
|
+
return True
|
|
546
|
+
|
|
547
|
+
async def _batched_async_contains(
|
|
548
|
+
self,
|
|
549
|
+
keys: List[CacheEngineKey],
|
|
550
|
+
) -> int:
|
|
551
|
+
"""Internal: asynchronously check how many consecutive keys exist.
|
|
552
|
+
|
|
553
|
+
Fans out individual EXISTS checks across the thread pool.
|
|
554
|
+
"""
|
|
555
|
+
key_strs = [k.to_string() for k in keys]
|
|
556
|
+
wrapped = [asyncio.wrap_future(self._pool.submit_exists(k)) for k in key_strs]
|
|
557
|
+
results = await asyncio.gather(*wrapped)
|
|
558
|
+
for i, r in enumerate(results):
|
|
559
|
+
if not r:
|
|
560
|
+
return i
|
|
561
|
+
return len(results)
|
|
562
|
+
|
|
563
|
+
async def batched_async_contains(
|
|
564
|
+
self,
|
|
565
|
+
lookup_id: str,
|
|
566
|
+
keys: List[CacheEngineKey],
|
|
567
|
+
pin: bool = False,
|
|
568
|
+
) -> int:
|
|
569
|
+
"""Asynchronously check how many consecutive keys exist."""
|
|
570
|
+
return await self._pq_executor.submit_job(
|
|
571
|
+
self._batched_async_contains,
|
|
572
|
+
keys=keys,
|
|
573
|
+
priority=Priorities.PREFETCH,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
def support_batched_get_non_blocking(self) -> bool:
|
|
577
|
+
"""Returns True — non-blocking batched get is supported."""
|
|
578
|
+
return True
|
|
579
|
+
|
|
580
|
+
async def _batched_get_non_blocking(
|
|
581
|
+
self,
|
|
582
|
+
keys: List[CacheEngineKey],
|
|
583
|
+
) -> List[MemoryObj]:
|
|
584
|
+
"""Internal: non-blocking batched get returning the consecutive prefix.
|
|
585
|
+
|
|
586
|
+
Only the consecutive prefix of non-None memory objects (from the
|
|
587
|
+
beginning) is returned. Once a ``None`` (missing key or allocation
|
|
588
|
+
failure) is encountered, all subsequent objects — even if they were
|
|
589
|
+
successfully retrieved — are released and excluded. This matches
|
|
590
|
+
the base-class contract.
|
|
591
|
+
"""
|
|
592
|
+
all_results = await self._batched_get(keys)
|
|
593
|
+
|
|
594
|
+
prefix: List[MemoryObj] = []
|
|
595
|
+
found_failure = False
|
|
596
|
+
for result in all_results:
|
|
597
|
+
if found_failure:
|
|
598
|
+
if result is not None:
|
|
599
|
+
result.ref_count_down()
|
|
600
|
+
elif result is not None:
|
|
601
|
+
prefix.append(result)
|
|
602
|
+
else:
|
|
603
|
+
found_failure = True
|
|
604
|
+
|
|
605
|
+
return prefix
|
|
606
|
+
|
|
607
|
+
async def batched_get_non_blocking(
|
|
608
|
+
self,
|
|
609
|
+
lookup_id: str,
|
|
610
|
+
keys: List[CacheEngineKey],
|
|
611
|
+
) -> List[MemoryObj]:
|
|
612
|
+
"""Non-blocking batched get returning the consecutive prefix."""
|
|
613
|
+
return await self._pq_executor.submit_job(
|
|
614
|
+
self._batched_get_non_blocking,
|
|
615
|
+
keys=keys,
|
|
616
|
+
priority=Priorities.PREFETCH,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
async def list(self) -> List[str]:
|
|
620
|
+
"""List all keys (not implemented)."""
|
|
621
|
+
return []
|
|
622
|
+
|
|
623
|
+
async def close(self) -> None:
|
|
624
|
+
"""Shut down the PQ executor and the thread pool."""
|
|
625
|
+
await self._pq_executor.shutdown_async(wait=True)
|
|
626
|
+
self._pool.close()
|
|
627
|
+
logger.info("Closed ValkeyConnector")
|