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,135 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Mooncake Store native L2 adapter config and factory.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Future
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# Standard
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Dict,
|
|
13
|
+
Optional,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from lmcache.v1.distributed.internal_api import (
|
|
18
|
+
L1MemoryDesc,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# First Party
|
|
22
|
+
from lmcache.logging import init_logger
|
|
23
|
+
from lmcache.v1.distributed.l2_adapters.base import (
|
|
24
|
+
L2AdapterInterface,
|
|
25
|
+
)
|
|
26
|
+
from lmcache.v1.distributed.l2_adapters.config import (
|
|
27
|
+
L2AdapterConfigBase,
|
|
28
|
+
register_l2_adapter_type,
|
|
29
|
+
)
|
|
30
|
+
from lmcache.v1.distributed.l2_adapters.factory import (
|
|
31
|
+
register_l2_adapter_factory,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = init_logger(__name__)
|
|
35
|
+
|
|
36
|
+
# Keys consumed only by LMCache (never sent to mooncake).
|
|
37
|
+
_LMCACHE_ONLY_KEYS = {"type", "num_workers", "eviction"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MooncakeStoreL2AdapterConfig(L2AdapterConfigBase):
|
|
41
|
+
"""Config for an L2 adapter backed by the native
|
|
42
|
+
C++ Mooncake Store connector.
|
|
43
|
+
|
|
44
|
+
``setup_config`` is a string-to-string dict that is
|
|
45
|
+
forwarded **as-is** to mooncake's
|
|
46
|
+
``RealClient::setup_internal(ConfigDict)``.
|
|
47
|
+
LMCache does NOT interpret, validate, or fill in
|
|
48
|
+
defaults for any mooncake keys — that is mooncake's
|
|
49
|
+
responsibility.
|
|
50
|
+
|
|
51
|
+
``num_workers`` is the only LMCache-specific knob.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
setup_config: Dict[str, str],
|
|
57
|
+
num_workers: int = 4,
|
|
58
|
+
):
|
|
59
|
+
super().__init__()
|
|
60
|
+
self.setup_config: Dict[str, str] = dict(setup_config)
|
|
61
|
+
self.num_workers = num_workers
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_dict(cls, d: dict) -> "MooncakeStoreL2AdapterConfig":
|
|
65
|
+
num_workers = d.get("num_workers", 4)
|
|
66
|
+
if not isinstance(num_workers, int) or num_workers <= 0:
|
|
67
|
+
raise ValueError("num_workers must be a positive integer")
|
|
68
|
+
|
|
69
|
+
# Everything except LMCache-only keys is
|
|
70
|
+
# forwarded to mooncake as str values.
|
|
71
|
+
setup: Dict[str, str] = {}
|
|
72
|
+
for k, v in d.items():
|
|
73
|
+
if k in _LMCACHE_ONLY_KEYS:
|
|
74
|
+
continue
|
|
75
|
+
if v is not None:
|
|
76
|
+
setup[k] = str(v)
|
|
77
|
+
|
|
78
|
+
return cls(
|
|
79
|
+
setup_config=setup,
|
|
80
|
+
num_workers=num_workers,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def help(cls) -> str:
|
|
85
|
+
return (
|
|
86
|
+
"Mooncake Store L2 adapter config.\n"
|
|
87
|
+
"All keys except LMCache-only keys are "
|
|
88
|
+
"forwarded as-is to mooncake's "
|
|
89
|
+
"setup_internal(ConfigDict).\n"
|
|
90
|
+
"Refer to mooncake documentation for "
|
|
91
|
+
"available setup keys.\n"
|
|
92
|
+
"- num_workers (int): C++ worker threads "
|
|
93
|
+
"(default 4, >0)"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _create_mooncake_store_l2_adapter(
|
|
98
|
+
config: L2AdapterConfigBase,
|
|
99
|
+
l1_memory_desc: "Optional[L1MemoryDesc]" = None,
|
|
100
|
+
) -> L2AdapterInterface:
|
|
101
|
+
"""Create a NativeConnectorL2Adapter backed by the
|
|
102
|
+
C++ Mooncake Store connector."""
|
|
103
|
+
try:
|
|
104
|
+
# First Party
|
|
105
|
+
from lmcache.lmcache_mooncake import (
|
|
106
|
+
LMCacheMooncakeClient,
|
|
107
|
+
)
|
|
108
|
+
except ImportError as e:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
"Mooncake Store L2 adapter requires the "
|
|
111
|
+
"C++ Mooncake extension. Build with: "
|
|
112
|
+
"MOONCAKE_INCLUDE_DIR=/path/to/mooncake-"
|
|
113
|
+
"store/include pip install -e ."
|
|
114
|
+
) from e
|
|
115
|
+
|
|
116
|
+
# First Party
|
|
117
|
+
from lmcache.v1.distributed.l2_adapters.native_connector_l2_adapter import ( # noqa: E501
|
|
118
|
+
NativeConnectorL2Adapter,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert isinstance(config, MooncakeStoreL2AdapterConfig)
|
|
122
|
+
native_client = LMCacheMooncakeClient(
|
|
123
|
+
config=config.setup_config,
|
|
124
|
+
num_workers=config.num_workers,
|
|
125
|
+
)
|
|
126
|
+
logger.info(
|
|
127
|
+
"Created Mooncake Store L2 adapter (workers=%d)",
|
|
128
|
+
config.num_workers,
|
|
129
|
+
)
|
|
130
|
+
return NativeConnectorL2Adapter(native_client)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Self-register config type and adapter factory
|
|
134
|
+
register_l2_adapter_type("mooncake_store", MooncakeStoreL2AdapterConfig)
|
|
135
|
+
register_l2_adapter_factory("mooncake_store", _create_mooncake_store_l2_adapter)
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
L2 adapter that wraps any pybind-wrapped C++ IStorageConnector (native client).
|
|
4
|
+
|
|
5
|
+
This bridge lets any native storage connector (Redis, RDMA, Mooncake, etc.)
|
|
6
|
+
serve as an MP-mode L2 adapter. The same C++ connector implementation is
|
|
7
|
+
also usable in non-MP mode via ConnectorClientBase.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
- The native client has 1 eventfd + drain_completions() for all operations.
|
|
11
|
+
- This adapter creates 3 Python eventfds (store, lookup, load) and runs a
|
|
12
|
+
background demux thread that routes native completions to the right
|
|
13
|
+
category based on a future_id → op_type mapping.
|
|
14
|
+
- ObjectKey serialization and MemoryObj buffer extraction happen at the
|
|
15
|
+
submit call boundary.
|
|
16
|
+
- Locking is client-side (refcount dict) since remote backends don't have
|
|
17
|
+
our eviction concept.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Future
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
# Standard
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
import os
|
|
26
|
+
import select
|
|
27
|
+
import threading
|
|
28
|
+
|
|
29
|
+
# First Party
|
|
30
|
+
from lmcache.logging import init_logger
|
|
31
|
+
from lmcache.native_storage_ops import Bitmap
|
|
32
|
+
from lmcache.v1.distributed.api import ObjectKey
|
|
33
|
+
from lmcache.v1.distributed.l2_adapters.base import (
|
|
34
|
+
L2AdapterInterface,
|
|
35
|
+
L2TaskId,
|
|
36
|
+
)
|
|
37
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
38
|
+
|
|
39
|
+
logger = init_logger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Key separator — kept in sync with fs_l2_adapter.py and
|
|
43
|
+
# csrc/storage_backends/fs/connector.cpp. Both ``@`` in ``model_name``
|
|
44
|
+
# and ``@`` in ``cache_salt`` are rejected by ObjectKey.__post_init__
|
|
45
|
+
# so splitting on ``@`` is unambiguous.
|
|
46
|
+
_KEY_SEP = "@"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _object_key_to_string(key: ObjectKey) -> str:
|
|
50
|
+
"""Serialize an ObjectKey to the native-connector wire format.
|
|
51
|
+
|
|
52
|
+
Unsalted::
|
|
53
|
+
|
|
54
|
+
<model_name>@<kv_rank_hex>@<chunk_hash_hex>
|
|
55
|
+
|
|
56
|
+
Salted (trailing ``cache_salt``)::
|
|
57
|
+
|
|
58
|
+
<model_name>@<kv_rank_hex>@<chunk_hash_hex>@<cache_salt>
|
|
59
|
+
|
|
60
|
+
Keys with ``cache_salt=""`` produce the 3-field shape, which is
|
|
61
|
+
bit-identical to the format used before ``cache_salt`` existed —
|
|
62
|
+
so existing un-salted caches remain valid with no migration.
|
|
63
|
+
"""
|
|
64
|
+
base = (
|
|
65
|
+
f"{key.model_name}{_KEY_SEP}{key.kv_rank:08x}{_KEY_SEP}{key.chunk_hash.hex()}"
|
|
66
|
+
)
|
|
67
|
+
if key.cache_salt:
|
|
68
|
+
return f"{base}{_KEY_SEP}{key.cache_salt}"
|
|
69
|
+
return base
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _obj_to_memoryview(
|
|
73
|
+
obj: MemoryObj,
|
|
74
|
+
) -> memoryview: # type: ignore[type-arg]
|
|
75
|
+
"""
|
|
76
|
+
Extract a byte-oriented memoryview from a MemoryObj.
|
|
77
|
+
|
|
78
|
+
Uses the MemoryObj's byte_array property which returns
|
|
79
|
+
a ctypes-backed memoryview with itemsize=1, so pybind's
|
|
80
|
+
buffer_info.size == num_bytes.
|
|
81
|
+
"""
|
|
82
|
+
return obj.byte_array # type: ignore[return-value]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class NativeConnectorL2Adapter(L2AdapterInterface):
|
|
86
|
+
"""
|
|
87
|
+
Wraps a pybind-wrapped C++ IStorageConnector to
|
|
88
|
+
implement L2AdapterInterface.
|
|
89
|
+
|
|
90
|
+
The native_client must expose:
|
|
91
|
+
- event_fd() -> int
|
|
92
|
+
- submit_batch_get(keys, memoryviews) -> int
|
|
93
|
+
- submit_batch_set(keys, memoryviews) -> int
|
|
94
|
+
- submit_batch_exists(keys) -> int
|
|
95
|
+
- drain_completions()
|
|
96
|
+
-> list[tuple[int, bool, str, list[bool]|None]]
|
|
97
|
+
- close()
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# Operation type tags for the pending-ops map
|
|
101
|
+
_OP_STORE = "store"
|
|
102
|
+
_OP_LOOKUP = "lookup"
|
|
103
|
+
_OP_LOAD = "load"
|
|
104
|
+
_OP_DELETE = "delete"
|
|
105
|
+
|
|
106
|
+
def __init__(self, native_client, max_capacity_gb: float = 0):
|
|
107
|
+
super().__init__()
|
|
108
|
+
self._client = native_client
|
|
109
|
+
self._client_fd: int = int(native_client.event_fd())
|
|
110
|
+
|
|
111
|
+
# 3 distinct Python eventfds for the L2 adapter
|
|
112
|
+
# interface
|
|
113
|
+
self._store_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
114
|
+
self._lookup_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
115
|
+
self._load_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
116
|
+
|
|
117
|
+
# Pending ops: native future_id →
|
|
118
|
+
# (op_type, task_id, num_keys, keys_for_locking)
|
|
119
|
+
# keys_for_locking is only set for lookup ops so
|
|
120
|
+
# we can apply locks
|
|
121
|
+
self._pending_ops: dict[
|
|
122
|
+
int,
|
|
123
|
+
tuple[str, L2TaskId, int, list[ObjectKey] | None],
|
|
124
|
+
] = {}
|
|
125
|
+
|
|
126
|
+
# Completed results (same pattern as MockL2Adapter)
|
|
127
|
+
self._completed_stores: dict[L2TaskId, bool] = {}
|
|
128
|
+
self._completed_lookups: dict[L2TaskId, Bitmap] = {}
|
|
129
|
+
self._completed_loads: dict[L2TaskId, Bitmap] = {}
|
|
130
|
+
|
|
131
|
+
# Client-side lock tracking (refcount per key)
|
|
132
|
+
self._locked_keys: dict[ObjectKey, int] = defaultdict(int)
|
|
133
|
+
|
|
134
|
+
# Delete capability detection
|
|
135
|
+
self._has_delete = callable(getattr(native_client, "submit_batch_delete", None))
|
|
136
|
+
|
|
137
|
+
# Pending delete events for synchronous delete() calls
|
|
138
|
+
self._pending_delete_events: dict[L2TaskId, threading.Event] = {}
|
|
139
|
+
|
|
140
|
+
# Client-side size tracking for get_usage()
|
|
141
|
+
self._max_capacity_bytes = int(max_capacity_gb * (1024**3))
|
|
142
|
+
self._current_size_bytes: int = 0
|
|
143
|
+
self._key_sizes: dict[ObjectKey, int] = {}
|
|
144
|
+
# Pending store sizes: native future_id -> (keys, per_key_sizes)
|
|
145
|
+
self._pending_store_sizes: dict[int, tuple[list[ObjectKey], list[int]]] = {}
|
|
146
|
+
|
|
147
|
+
# Task ID counter
|
|
148
|
+
self._next_task_id: L2TaskId = 0
|
|
149
|
+
|
|
150
|
+
# Lock for all shared state above
|
|
151
|
+
self._lock = threading.Lock()
|
|
152
|
+
|
|
153
|
+
# Background demux thread
|
|
154
|
+
self._stop = threading.Event()
|
|
155
|
+
self._demux_thread = threading.Thread(
|
|
156
|
+
target=self._demux_loop,
|
|
157
|
+
daemon=True,
|
|
158
|
+
name="l2-adapter-demux",
|
|
159
|
+
)
|
|
160
|
+
self._demux_thread.start()
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------
|
|
163
|
+
# Event Fd Interface
|
|
164
|
+
# ---------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
def get_store_event_fd(self) -> int:
|
|
167
|
+
return self._store_efd
|
|
168
|
+
|
|
169
|
+
def get_lookup_and_lock_event_fd(self) -> int:
|
|
170
|
+
return self._lookup_efd
|
|
171
|
+
|
|
172
|
+
def get_load_event_fd(self) -> int:
|
|
173
|
+
return self._load_efd
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------
|
|
176
|
+
# Store Interface
|
|
177
|
+
# ---------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def submit_store_task(
|
|
180
|
+
self,
|
|
181
|
+
keys: list[ObjectKey],
|
|
182
|
+
objects: list[MemoryObj],
|
|
183
|
+
) -> L2TaskId:
|
|
184
|
+
key_strings = [_object_key_to_string(k) for k in keys]
|
|
185
|
+
memviews = [_obj_to_memoryview(obj) for obj in objects]
|
|
186
|
+
per_key_sizes = [obj.get_size() for obj in objects]
|
|
187
|
+
|
|
188
|
+
# Register pending op BEFORE submit to avoid race
|
|
189
|
+
# with demux thread. The native submit is
|
|
190
|
+
# non-blocking so holding the lock is brief.
|
|
191
|
+
with self._lock:
|
|
192
|
+
task_id = self._get_next_task_id()
|
|
193
|
+
future_id = int(self._client.submit_batch_set(key_strings, memviews))
|
|
194
|
+
self._pending_ops[future_id] = (
|
|
195
|
+
self._OP_STORE,
|
|
196
|
+
task_id,
|
|
197
|
+
len(keys),
|
|
198
|
+
None,
|
|
199
|
+
)
|
|
200
|
+
self._pending_store_sizes[future_id] = (list(keys), per_key_sizes)
|
|
201
|
+
|
|
202
|
+
return task_id
|
|
203
|
+
|
|
204
|
+
def pop_completed_store_tasks(
|
|
205
|
+
self,
|
|
206
|
+
) -> dict[L2TaskId, bool]:
|
|
207
|
+
with self._lock:
|
|
208
|
+
completed = self._completed_stores
|
|
209
|
+
self._completed_stores = {}
|
|
210
|
+
return completed
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------
|
|
213
|
+
# Lookup and Lock Interface
|
|
214
|
+
# ---------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
def submit_lookup_and_lock_task(
|
|
217
|
+
self,
|
|
218
|
+
keys: list[ObjectKey],
|
|
219
|
+
) -> L2TaskId:
|
|
220
|
+
key_strings = [_object_key_to_string(k) for k in keys]
|
|
221
|
+
|
|
222
|
+
with self._lock:
|
|
223
|
+
task_id = self._get_next_task_id()
|
|
224
|
+
future_id = int(self._client.submit_batch_exists(key_strings))
|
|
225
|
+
self._pending_ops[future_id] = (
|
|
226
|
+
self._OP_LOOKUP,
|
|
227
|
+
task_id,
|
|
228
|
+
len(keys),
|
|
229
|
+
list(keys),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return task_id
|
|
233
|
+
|
|
234
|
+
def query_lookup_and_lock_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
235
|
+
with self._lock:
|
|
236
|
+
return self._completed_lookups.pop(task_id, None)
|
|
237
|
+
|
|
238
|
+
def submit_unlock(self, keys: list[ObjectKey]) -> None:
|
|
239
|
+
with self._lock:
|
|
240
|
+
for key in keys:
|
|
241
|
+
if key not in self._locked_keys:
|
|
242
|
+
continue
|
|
243
|
+
if self._locked_keys[key] <= 1:
|
|
244
|
+
del self._locked_keys[key]
|
|
245
|
+
else:
|
|
246
|
+
self._locked_keys[key] -= 1
|
|
247
|
+
|
|
248
|
+
# ---------------------------------------------------------------
|
|
249
|
+
# Load Interface
|
|
250
|
+
# ---------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
def submit_load_task(
|
|
253
|
+
self,
|
|
254
|
+
keys: list[ObjectKey],
|
|
255
|
+
objects: list[MemoryObj],
|
|
256
|
+
) -> L2TaskId:
|
|
257
|
+
key_strings = [_object_key_to_string(k) for k in keys]
|
|
258
|
+
memviews = [_obj_to_memoryview(obj) for obj in objects]
|
|
259
|
+
|
|
260
|
+
with self._lock:
|
|
261
|
+
task_id = self._get_next_task_id()
|
|
262
|
+
future_id = int(self._client.submit_batch_get(key_strings, memviews))
|
|
263
|
+
self._pending_ops[future_id] = (
|
|
264
|
+
self._OP_LOAD,
|
|
265
|
+
task_id,
|
|
266
|
+
len(keys),
|
|
267
|
+
list(keys),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return task_id
|
|
271
|
+
|
|
272
|
+
def query_load_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
273
|
+
with self._lock:
|
|
274
|
+
return self._completed_loads.pop(task_id, None)
|
|
275
|
+
|
|
276
|
+
# ---------------------------------------------------------------
|
|
277
|
+
# Eviction Interface
|
|
278
|
+
# ---------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
def delete(self, keys: list[ObjectKey]) -> None:
|
|
281
|
+
"""Delete a batch of keys from the remote backend.
|
|
282
|
+
|
|
283
|
+
Submits a batch delete to the native connector and blocks
|
|
284
|
+
until the demux thread signals completion (up to 30s timeout).
|
|
285
|
+
Fires ``_notify_keys_deleted`` on success so eviction policy
|
|
286
|
+
tracking stays in sync.
|
|
287
|
+
|
|
288
|
+
No-op if the connector does not expose ``submit_batch_delete``
|
|
289
|
+
or if the key list is empty.
|
|
290
|
+
"""
|
|
291
|
+
if not keys or not self._has_delete:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
key_strings = [_object_key_to_string(k) for k in keys]
|
|
295
|
+
done_event = threading.Event()
|
|
296
|
+
|
|
297
|
+
with self._lock:
|
|
298
|
+
task_id = self._get_next_task_id()
|
|
299
|
+
future_id = int(self._client.submit_batch_delete(key_strings))
|
|
300
|
+
self._pending_ops[future_id] = (
|
|
301
|
+
self._OP_DELETE,
|
|
302
|
+
task_id,
|
|
303
|
+
len(keys),
|
|
304
|
+
list(keys),
|
|
305
|
+
)
|
|
306
|
+
self._pending_delete_events[task_id] = done_event
|
|
307
|
+
|
|
308
|
+
# Block until demux thread signals completion
|
|
309
|
+
if not done_event.wait(timeout=30.0):
|
|
310
|
+
with self._lock:
|
|
311
|
+
self._pending_delete_events.pop(task_id, None)
|
|
312
|
+
# Note: _pending_ops entry may already be consumed
|
|
313
|
+
# by the demux thread; pop is safe either way.
|
|
314
|
+
for fid, entry in list(self._pending_ops.items()):
|
|
315
|
+
if entry[1] == task_id:
|
|
316
|
+
self._pending_ops.pop(fid, None)
|
|
317
|
+
break
|
|
318
|
+
logger.warning(
|
|
319
|
+
"delete() timed out after 30s for %d keys",
|
|
320
|
+
len(keys),
|
|
321
|
+
)
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
self._notify_keys_deleted(keys)
|
|
325
|
+
|
|
326
|
+
def get_usage(self) -> tuple[float, float]:
|
|
327
|
+
if self._max_capacity_bytes <= 0:
|
|
328
|
+
return (-1.0, -1.0)
|
|
329
|
+
with self._lock:
|
|
330
|
+
usage = self._current_size_bytes / self._max_capacity_bytes
|
|
331
|
+
return (usage, usage)
|
|
332
|
+
|
|
333
|
+
# ---------------------------------------------------------------
|
|
334
|
+
# Cleanup
|
|
335
|
+
# ---------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def close(self) -> None:
|
|
338
|
+
self._stop.set()
|
|
339
|
+
self._demux_thread.join(timeout=5)
|
|
340
|
+
|
|
341
|
+
self._client.close()
|
|
342
|
+
|
|
343
|
+
os.close(self._store_efd)
|
|
344
|
+
os.close(self._lookup_efd)
|
|
345
|
+
os.close(self._load_efd)
|
|
346
|
+
|
|
347
|
+
# ---------------------------------------------------------------
|
|
348
|
+
# Internal helpers
|
|
349
|
+
# ---------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def _get_next_task_id(self) -> L2TaskId:
|
|
352
|
+
"""Increment and return the next task ID.
|
|
353
|
+
Must be called under _lock."""
|
|
354
|
+
task_id = self._next_task_id
|
|
355
|
+
self._next_task_id += 1
|
|
356
|
+
return task_id
|
|
357
|
+
|
|
358
|
+
def _demux_loop(self) -> None:
|
|
359
|
+
"""Background thread that polls the native
|
|
360
|
+
connector's eventfd, drains completions, and
|
|
361
|
+
routes them to the correct L2 result category.
|
|
362
|
+
"""
|
|
363
|
+
poller = select.poll()
|
|
364
|
+
poller.register(self._client_fd, select.POLLIN)
|
|
365
|
+
|
|
366
|
+
while not self._stop.is_set():
|
|
367
|
+
events = poller.poll(500)
|
|
368
|
+
if not events:
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
completions = self._client.drain_completions()
|
|
373
|
+
except Exception:
|
|
374
|
+
logger.exception("drain_completions failed")
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
if not completions:
|
|
378
|
+
continue
|
|
379
|
+
|
|
380
|
+
# Collect listener notifications to fire after
|
|
381
|
+
# releasing the lock.
|
|
382
|
+
keys_stored: list[ObjectKey] = []
|
|
383
|
+
keys_accessed: list[ObjectKey] = []
|
|
384
|
+
|
|
385
|
+
with self._lock:
|
|
386
|
+
for (
|
|
387
|
+
future_id,
|
|
388
|
+
ok,
|
|
389
|
+
error,
|
|
390
|
+
result_bools,
|
|
391
|
+
) in completions:
|
|
392
|
+
fid = int(future_id)
|
|
393
|
+
entry = self._pending_ops.pop(fid, None)
|
|
394
|
+
if entry is None:
|
|
395
|
+
logger.warning(
|
|
396
|
+
"Received completion for unknown future_id=%d",
|
|
397
|
+
fid,
|
|
398
|
+
)
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
(
|
|
402
|
+
op_type,
|
|
403
|
+
task_id,
|
|
404
|
+
num_keys,
|
|
405
|
+
lookup_keys,
|
|
406
|
+
) = entry
|
|
407
|
+
|
|
408
|
+
if op_type == self._OP_STORE:
|
|
409
|
+
self._completed_stores[task_id] = ok
|
|
410
|
+
# Update size tracking on success
|
|
411
|
+
store_info = self._pending_store_sizes.pop(fid, None)
|
|
412
|
+
if ok and store_info is not None:
|
|
413
|
+
store_keys, sizes = store_info
|
|
414
|
+
for key, size in zip(store_keys, sizes, strict=False):
|
|
415
|
+
if key not in self._key_sizes:
|
|
416
|
+
self._key_sizes[key] = size
|
|
417
|
+
self._current_size_bytes += size
|
|
418
|
+
keys_stored.extend(store_keys)
|
|
419
|
+
os.eventfd_write(self._store_efd, 1)
|
|
420
|
+
|
|
421
|
+
elif op_type == self._OP_LOOKUP:
|
|
422
|
+
bitmap = Bitmap(num_keys)
|
|
423
|
+
if ok and result_bools is not None:
|
|
424
|
+
for i, found in enumerate(result_bools):
|
|
425
|
+
if found:
|
|
426
|
+
bitmap.set(i)
|
|
427
|
+
if lookup_keys is not None:
|
|
428
|
+
self._locked_keys[lookup_keys[i]] += 1
|
|
429
|
+
self._completed_lookups[task_id] = bitmap
|
|
430
|
+
os.eventfd_write(self._lookup_efd, 1)
|
|
431
|
+
|
|
432
|
+
elif op_type == self._OP_LOAD:
|
|
433
|
+
bitmap = Bitmap(num_keys)
|
|
434
|
+
loaded_keys: list[ObjectKey] = []
|
|
435
|
+
if result_bools is not None:
|
|
436
|
+
for i, loaded in enumerate(result_bools):
|
|
437
|
+
if loaded:
|
|
438
|
+
bitmap.set(i)
|
|
439
|
+
if lookup_keys is not None:
|
|
440
|
+
loaded_keys.append(lookup_keys[i])
|
|
441
|
+
elif ok:
|
|
442
|
+
# Fallback for connectors that
|
|
443
|
+
# do not report per-key results
|
|
444
|
+
for i in range(num_keys):
|
|
445
|
+
bitmap.set(i)
|
|
446
|
+
if lookup_keys is not None:
|
|
447
|
+
loaded_keys.extend(lookup_keys)
|
|
448
|
+
keys_accessed.extend(loaded_keys)
|
|
449
|
+
self._completed_loads[task_id] = bitmap
|
|
450
|
+
os.eventfd_write(self._load_efd, 1)
|
|
451
|
+
|
|
452
|
+
elif op_type == self._OP_DELETE:
|
|
453
|
+
# Decrement sizes for successfully deleted keys
|
|
454
|
+
if result_bools is not None and lookup_keys is not None:
|
|
455
|
+
for i, deleted in enumerate(result_bools):
|
|
456
|
+
if deleted:
|
|
457
|
+
key = lookup_keys[i]
|
|
458
|
+
size = self._key_sizes.pop(key, 0)
|
|
459
|
+
self._current_size_bytes -= size
|
|
460
|
+
evt = self._pending_delete_events.pop(task_id, None)
|
|
461
|
+
if evt is not None:
|
|
462
|
+
evt.set()
|
|
463
|
+
|
|
464
|
+
# Fire listener notifications outside the lock
|
|
465
|
+
if keys_stored:
|
|
466
|
+
self._notify_keys_stored(keys_stored)
|
|
467
|
+
if keys_accessed:
|
|
468
|
+
self._notify_keys_accessed(keys_accessed)
|