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,193 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Prefetch policy interface and default implementation for L2-to-L1 load decisions.
|
|
4
|
+
|
|
5
|
+
The prefetch policy decides which L2 adapter should load each key when multiple
|
|
6
|
+
adapters have the same key. It receives lookup results (bitmaps) from all adapters
|
|
7
|
+
and produces a load plan mapping each adapter to the key indices it should load.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Standard
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
|
|
13
|
+
# First Party
|
|
14
|
+
from lmcache.native_storage_ops import Bitmap
|
|
15
|
+
from lmcache.v1.distributed.api import ObjectKey
|
|
16
|
+
from lmcache.v1.distributed.storage_controllers.store_policy import (
|
|
17
|
+
AdapterDescriptor,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrefetchPolicy(ABC):
|
|
22
|
+
"""
|
|
23
|
+
Abstract interface for prefetch load-plan decisions.
|
|
24
|
+
|
|
25
|
+
The prefetch policy is called by the PrefetchController after all L2
|
|
26
|
+
adapters have completed their lookup_and_lock operations. Given the
|
|
27
|
+
lookup results, it decides which adapter should load which keys.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def select_load_plan(
|
|
32
|
+
self,
|
|
33
|
+
keys: list[ObjectKey],
|
|
34
|
+
lookup_results: dict[int, Bitmap],
|
|
35
|
+
adapters: list[AdapterDescriptor],
|
|
36
|
+
) -> dict[int, Bitmap]:
|
|
37
|
+
"""
|
|
38
|
+
Decide which adapter loads which keys.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
keys: Full list of keys being prefetched from L2.
|
|
42
|
+
lookup_results: Mapping from adapter index to Bitmap.
|
|
43
|
+
A set bit at position i means the adapter has keys[i].
|
|
44
|
+
adapters: Descriptors of available L2 adapters.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Mapping from adapter index to a bitmap telling which keys that
|
|
48
|
+
the adapter should load. The returned bitmaps should not
|
|
49
|
+
overlap, and the union of all returned bitmaps should be a subset
|
|
50
|
+
of the union of the input bitmaps.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def select_l1_retentions(
|
|
54
|
+
self,
|
|
55
|
+
keys: list[ObjectKey],
|
|
56
|
+
) -> list[bool]:
|
|
57
|
+
"""Determine which keys to retain in L1 after prefetched
|
|
58
|
+
objects are consumed.
|
|
59
|
+
|
|
60
|
+
Called by PrefetchController just before
|
|
61
|
+
``l1_mgr.reserve_write`` to build the ``is_temporary``
|
|
62
|
+
flags. A ``True`` value means the key is retained
|
|
63
|
+
(permanent); ``False`` means it is temporary and will be
|
|
64
|
+
deleted after the reader finishes.
|
|
65
|
+
|
|
66
|
+
The default implementation marks all keys as temporary
|
|
67
|
+
(not retained). Override in subclasses to implement
|
|
68
|
+
hot-cache or selective-retention strategies.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
keys: Keys about to be written into L1.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A list of bools with the same length as *keys*.
|
|
75
|
+
``True`` = retain (permanent), ``False`` = temporary.
|
|
76
|
+
"""
|
|
77
|
+
return [False] * len(keys)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# -----------------------------------------------------------------------------
|
|
81
|
+
# Registry: prefetch policy name -> policy class
|
|
82
|
+
# -----------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
_PREFETCH_POLICY_REGISTRY: dict[str, type[PrefetchPolicy]] = {}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def register_prefetch_policy(
|
|
88
|
+
name: str,
|
|
89
|
+
policy_cls: type[PrefetchPolicy],
|
|
90
|
+
) -> None:
|
|
91
|
+
"""
|
|
92
|
+
Register a prefetch policy class under a name.
|
|
93
|
+
|
|
94
|
+
Each policy module should call this at import time.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
name: Policy name (e.g. "default").
|
|
98
|
+
policy_cls: A concrete PrefetchPolicy subclass.
|
|
99
|
+
"""
|
|
100
|
+
if name in _PREFETCH_POLICY_REGISTRY:
|
|
101
|
+
raise ValueError(f"Prefetch policy already registered: {name!r}")
|
|
102
|
+
_PREFETCH_POLICY_REGISTRY[name] = policy_cls
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_registered_prefetch_policies() -> list[str]:
|
|
106
|
+
"""Return the list of registered prefetch policy names."""
|
|
107
|
+
return list(_PREFETCH_POLICY_REGISTRY)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def create_prefetch_policy(name: str) -> PrefetchPolicy:
|
|
111
|
+
"""
|
|
112
|
+
Create a prefetch policy instance by name.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: Registered policy name.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A new PrefetchPolicy instance.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If no policy is registered under the given name.
|
|
122
|
+
"""
|
|
123
|
+
if name not in _PREFETCH_POLICY_REGISTRY:
|
|
124
|
+
known = ", ".join(sorted(_PREFETCH_POLICY_REGISTRY)) or "(none)"
|
|
125
|
+
raise ValueError(f"Unknown prefetch policy {name!r}. Known: {known}")
|
|
126
|
+
return _PREFETCH_POLICY_REGISTRY[name]()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DefaultPrefetchPolicy(PrefetchPolicy):
|
|
130
|
+
"""
|
|
131
|
+
Default prefetch policy: for each key, pick the first adapter
|
|
132
|
+
(lowest index) that has it.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def select_load_plan(
|
|
136
|
+
self,
|
|
137
|
+
keys: list[ObjectKey],
|
|
138
|
+
lookup_results: dict[int, Bitmap],
|
|
139
|
+
adapters: list[AdapterDescriptor],
|
|
140
|
+
) -> dict[int, Bitmap]:
|
|
141
|
+
"""
|
|
142
|
+
Assign each key to the first adapter (by index) that has it.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
keys: Full list of keys being prefetched.
|
|
146
|
+
lookup_results: Adapter index -> Bitmap of lookup hits.
|
|
147
|
+
adapters: Descriptors of available L2 adapters.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Mapping from adapter index to key bitmaps. Each key goes
|
|
151
|
+
to the lowest-indexed adapter that reported having it.
|
|
152
|
+
"""
|
|
153
|
+
plan: dict[int, Bitmap] = {}
|
|
154
|
+
global_bitmap = Bitmap(len(keys))
|
|
155
|
+
for bitmap in lookup_results.values():
|
|
156
|
+
global_bitmap |= bitmap
|
|
157
|
+
|
|
158
|
+
for ad in sorted(adapters, key=lambda a: a.index):
|
|
159
|
+
curr_bitmap = lookup_results.get(ad.index)
|
|
160
|
+
if curr_bitmap is None:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
local_bitmap = global_bitmap & curr_bitmap
|
|
164
|
+
global_bitmap &= ~local_bitmap
|
|
165
|
+
if local_bitmap.popcount() == 0:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
plan[ad.index] = local_bitmap
|
|
169
|
+
|
|
170
|
+
return plan
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class RetainPrefetchPolicy(DefaultPrefetchPolicy):
|
|
174
|
+
"""Prefetch policy that retains all prefetched keys in L1.
|
|
175
|
+
|
|
176
|
+
Inherits ``select_load_plan`` from ``DefaultPrefetchPolicy``
|
|
177
|
+
(first-adapter-wins) and only overrides the L1 retention
|
|
178
|
+
decision: all prefetched keys become permanent.
|
|
179
|
+
|
|
180
|
+
Use this when prefetched data is likely to be reused by
|
|
181
|
+
subsequent requests (e.g. shared system-prompt chunks).
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def select_l1_retentions(
|
|
185
|
+
self,
|
|
186
|
+
keys: list[ObjectKey],
|
|
187
|
+
) -> list[bool]:
|
|
188
|
+
"""Retain all prefetched keys permanently in L1."""
|
|
189
|
+
return [True] * len(keys)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
register_prefetch_policy("default", DefaultPrefetchPolicy)
|
|
193
|
+
register_prefetch_policy("retain", RetainPrefetchPolicy)
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Store Controller: asynchronously copies data from L1 to L2 after writes complete.
|
|
4
|
+
|
|
5
|
+
The controller runs a background thread with an event-driven loop that:
|
|
6
|
+
1. Listens for L1 write-completion events via StoreListener.
|
|
7
|
+
2. Submits store tasks to L2 adapters based on StorePolicy decisions.
|
|
8
|
+
3. Monitors L2 task completion via event fds.
|
|
9
|
+
4. Releases L1 read locks and optionally deletes keys from L1.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Standard
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import os
|
|
15
|
+
import select
|
|
16
|
+
import threading
|
|
17
|
+
|
|
18
|
+
# First Party
|
|
19
|
+
from lmcache.logging import init_logger
|
|
20
|
+
from lmcache.v1.distributed.api import ObjectKey
|
|
21
|
+
from lmcache.v1.distributed.error import L1Error
|
|
22
|
+
from lmcache.v1.distributed.internal_api import L1ManagerListener
|
|
23
|
+
from lmcache.v1.distributed.l1_manager import L1Manager
|
|
24
|
+
from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
|
|
25
|
+
from lmcache.v1.distributed.storage_controller import StorageControllerInterface
|
|
26
|
+
from lmcache.v1.distributed.storage_controllers.store_policy import (
|
|
27
|
+
AdapterDescriptor,
|
|
28
|
+
StorePolicy,
|
|
29
|
+
)
|
|
30
|
+
from lmcache.v1.mp_observability.event import Event, EventType
|
|
31
|
+
from lmcache.v1.mp_observability.event_bus import get_event_bus
|
|
32
|
+
|
|
33
|
+
logger = init_logger(__name__)
|
|
34
|
+
|
|
35
|
+
# Poll timeout in milliseconds for the store loop
|
|
36
|
+
STORE_LOOP_POLL_TIMEOUT_MS = 500
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Helper classes (module-level, before main class)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StoreListener(L1ManagerListener):
|
|
43
|
+
"""
|
|
44
|
+
Listener that receives L1 write-completion callbacks and enqueues
|
|
45
|
+
keys for the StoreController's background loop.
|
|
46
|
+
|
|
47
|
+
The ``on_keys_write_finished`` callback is invoked inside L1Manager's
|
|
48
|
+
lock, so it must be non-blocking. It appends keys to an internal list
|
|
49
|
+
and signals an eventfd to wake up the controller's select.poll().
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
self._pending_keys: list[ObjectKey] = []
|
|
54
|
+
self._lock = threading.Lock()
|
|
55
|
+
self._event_fd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
56
|
+
|
|
57
|
+
def get_event_fd(self) -> int:
|
|
58
|
+
"""
|
|
59
|
+
Return the eventfd that is signaled when new keys are available.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
int: The eventfd file descriptor.
|
|
63
|
+
"""
|
|
64
|
+
return self._event_fd
|
|
65
|
+
|
|
66
|
+
def pop_pending_keys(self) -> list[ObjectKey]:
|
|
67
|
+
"""
|
|
68
|
+
Pop all pending keys from the queue.
|
|
69
|
+
|
|
70
|
+
This is non-blocking and should be called by the StoreController's
|
|
71
|
+
main loop after select.poll() indicates the eventfd is ready.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
list[ObjectKey]: All keys enqueued since the last pop.
|
|
75
|
+
"""
|
|
76
|
+
with self._lock:
|
|
77
|
+
keys = self._pending_keys
|
|
78
|
+
self._pending_keys = []
|
|
79
|
+
return keys
|
|
80
|
+
|
|
81
|
+
def pending_count(self) -> int:
|
|
82
|
+
"""Return the number of pending keys waiting to be processed."""
|
|
83
|
+
with self._lock:
|
|
84
|
+
return len(self._pending_keys)
|
|
85
|
+
|
|
86
|
+
# L1ManagerListener implementation
|
|
87
|
+
|
|
88
|
+
def on_l1_keys_write_finished(self, keys: list[ObjectKey]) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Enqueue keys and signal the eventfd.
|
|
91
|
+
|
|
92
|
+
Called inside L1Manager's lock. Must be fast and must not
|
|
93
|
+
call any L1Manager methods (would deadlock).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
keys (list[ObjectKey]): Keys that finished writing.
|
|
97
|
+
"""
|
|
98
|
+
with self._lock:
|
|
99
|
+
self._pending_keys.extend(keys)
|
|
100
|
+
os.eventfd_write(self._event_fd, 1)
|
|
101
|
+
|
|
102
|
+
def on_l1_keys_reserved_read(self, keys: list[ObjectKey]) -> None:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def on_l1_keys_read_finished(self, keys: list[ObjectKey]) -> None:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def on_l1_keys_reserved_write(self, keys: list[ObjectKey]) -> None:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
def on_l1_keys_deleted_by_manager(self, keys: list[ObjectKey]) -> None:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
def on_l1_keys_finish_write_and_reserve_read(self, keys: list[ObjectKey]) -> None:
|
|
115
|
+
# No op here because we don't want to trigger store when the
|
|
116
|
+
# objects are prefetched to L1.
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def on_l1_keys_accessed(self, keys: list[ObjectKey]) -> None:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
def close(self) -> None:
|
|
123
|
+
"""Close the eventfd."""
|
|
124
|
+
os.close(self._event_fd)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class InFlightStoreTask:
|
|
129
|
+
"""
|
|
130
|
+
Tracks a single submitted L2 store task so the controller can
|
|
131
|
+
release L1 read locks and perform cleanup when it completes.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
adapter_index: int
|
|
135
|
+
"""Which L2 adapter this task was submitted to."""
|
|
136
|
+
|
|
137
|
+
keys: list[ObjectKey]
|
|
138
|
+
"""All keys that were submitted in this store task."""
|
|
139
|
+
|
|
140
|
+
read_locked_keys: list[ObjectKey]
|
|
141
|
+
"""The subset of keys for which reserve_read succeeded
|
|
142
|
+
(i.e., keys holding an L1 read lock that must be released)."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Main class
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class StoreController(StorageControllerInterface):
|
|
149
|
+
"""
|
|
150
|
+
Asynchronously stores L1 data to L2 adapters after write completion.
|
|
151
|
+
|
|
152
|
+
The controller:
|
|
153
|
+
1. Registers a StoreListener with L1Manager to receive
|
|
154
|
+
on_keys_write_finished callbacks.
|
|
155
|
+
2. Runs a background thread with an event-driven loop using
|
|
156
|
+
select.poll() on the listener eventfd and all L2 adapter
|
|
157
|
+
store eventfds.
|
|
158
|
+
3. On new keys: consults StorePolicy to decide targets,
|
|
159
|
+
calls reserve_read to get MemoryObjs, and submits store
|
|
160
|
+
tasks to L2 adapters.
|
|
161
|
+
4. On L2 task completion: releases read locks, optionally
|
|
162
|
+
deletes keys from L1 per policy.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
l1_manager: The L1 manager instance.
|
|
166
|
+
l2_adapters: List of L2 adapter instances.
|
|
167
|
+
adapter_descriptors: Descriptors for each L2 adapter (same order).
|
|
168
|
+
policy: The store policy for deciding targets and deletions.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
l1_manager: L1Manager,
|
|
174
|
+
l2_adapters: list[L2AdapterInterface],
|
|
175
|
+
adapter_descriptors: list[AdapterDescriptor],
|
|
176
|
+
policy: StorePolicy,
|
|
177
|
+
) -> None:
|
|
178
|
+
self._l1_manager = l1_manager
|
|
179
|
+
self._l2_adapters = l2_adapters
|
|
180
|
+
self._adapter_descriptors = adapter_descriptors
|
|
181
|
+
self._policy = policy
|
|
182
|
+
|
|
183
|
+
self._listener = StoreListener()
|
|
184
|
+
self._l1_manager.register_listener(self._listener)
|
|
185
|
+
self._event_bus = get_event_bus()
|
|
186
|
+
|
|
187
|
+
# (adapter_index, task_id) -> InFlightStoreTask
|
|
188
|
+
# Composite key is needed because task IDs are only unique
|
|
189
|
+
# within a single adapter, not across adapters.
|
|
190
|
+
self._in_flight_tasks: dict[tuple[int, L2TaskId], InFlightStoreTask] = {}
|
|
191
|
+
|
|
192
|
+
# Shadow counter for status reporting (updated in background loop)
|
|
193
|
+
self._status_in_flight_count: int = 0
|
|
194
|
+
|
|
195
|
+
# Map eventfd -> adapter index for quick lookup in poll results
|
|
196
|
+
self._efd_to_adapter_index: dict[int, int] = {}
|
|
197
|
+
for i, adapter in enumerate(self._l2_adapters):
|
|
198
|
+
efd = adapter.get_store_event_fd()
|
|
199
|
+
self._efd_to_adapter_index[efd] = i
|
|
200
|
+
|
|
201
|
+
self._stop_flag = threading.Event()
|
|
202
|
+
self._thread = threading.Thread(
|
|
203
|
+
target=self._store_loop,
|
|
204
|
+
daemon=True,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def start(self) -> None:
|
|
208
|
+
"""Start the background store loop thread."""
|
|
209
|
+
logger.info("Starting StoreController...")
|
|
210
|
+
self._thread.start()
|
|
211
|
+
|
|
212
|
+
def stop(self) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Signal the loop to stop, wait for the thread to join.
|
|
215
|
+
|
|
216
|
+
Releases all in-flight read locks on shutdown so that
|
|
217
|
+
L1 objects are not permanently locked.
|
|
218
|
+
"""
|
|
219
|
+
self._stop_flag.set()
|
|
220
|
+
# Wake up the poll loop so it can exit promptly
|
|
221
|
+
os.eventfd_write(self._listener.get_event_fd(), 1)
|
|
222
|
+
self._thread.join()
|
|
223
|
+
self._cleanup_in_flight_tasks()
|
|
224
|
+
self._listener.close()
|
|
225
|
+
|
|
226
|
+
def report_status(self) -> dict:
|
|
227
|
+
"""Return a status dict for the store controller."""
|
|
228
|
+
is_healthy = self._thread.is_alive()
|
|
229
|
+
return {
|
|
230
|
+
"is_healthy": is_healthy,
|
|
231
|
+
"thread_alive": is_healthy,
|
|
232
|
+
"pending_keys_count": self._listener.pending_count(),
|
|
233
|
+
"in_flight_task_count": self._status_in_flight_count,
|
|
234
|
+
"num_l2_adapters": len(self._l2_adapters),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Private methods
|
|
238
|
+
|
|
239
|
+
def _store_loop(self) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Main event-driven loop running in a background thread.
|
|
242
|
+
|
|
243
|
+
Uses select.poll() to wait on:
|
|
244
|
+
- The StoreListener's eventfd (new keys from L1 writes).
|
|
245
|
+
- Each L2 adapter's store eventfd (completed store tasks).
|
|
246
|
+
|
|
247
|
+
Exits when the stop flag is set.
|
|
248
|
+
"""
|
|
249
|
+
poller = select.poll()
|
|
250
|
+
|
|
251
|
+
listener_efd = self._listener.get_event_fd()
|
|
252
|
+
poller.register(listener_efd, select.POLLIN)
|
|
253
|
+
|
|
254
|
+
for efd in self._efd_to_adapter_index:
|
|
255
|
+
poller.register(efd, select.POLLIN)
|
|
256
|
+
|
|
257
|
+
while not self._stop_flag.is_set():
|
|
258
|
+
ready = poller.poll(STORE_LOOP_POLL_TIMEOUT_MS)
|
|
259
|
+
|
|
260
|
+
for fd, events in ready:
|
|
261
|
+
if not (events & select.POLLIN):
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
# Consume the eventfd value
|
|
265
|
+
try:
|
|
266
|
+
os.eventfd_read(fd)
|
|
267
|
+
except (OSError, BlockingIOError):
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
if fd == listener_efd:
|
|
272
|
+
keys = self._listener.pop_pending_keys()
|
|
273
|
+
if keys:
|
|
274
|
+
self._process_new_keys(keys)
|
|
275
|
+
elif fd in self._efd_to_adapter_index:
|
|
276
|
+
adapter_index = self._efd_to_adapter_index[fd]
|
|
277
|
+
self._process_completed_tasks(adapter_index)
|
|
278
|
+
except Exception:
|
|
279
|
+
logger.exception(
|
|
280
|
+
"Unexpected error in store loop while processing fd %d",
|
|
281
|
+
fd,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def _process_new_keys(self, keys: list[ObjectKey]) -> None:
|
|
285
|
+
"""
|
|
286
|
+
Process a batch of newly written keys.
|
|
287
|
+
|
|
288
|
+
1. Ask the policy which adapters each key should go to.
|
|
289
|
+
2. For each adapter target, reserve read access on L1 to get
|
|
290
|
+
MemoryObj references (skip keys that fail — best-effort).
|
|
291
|
+
3. Submit store tasks to L2 adapters.
|
|
292
|
+
4. Track in-flight tasks for later cleanup.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
keys (list[ObjectKey]): Keys that finished writing to L1.
|
|
296
|
+
"""
|
|
297
|
+
plan = self._policy.select_store_targets(keys, self._adapter_descriptors)
|
|
298
|
+
|
|
299
|
+
l1_mgr = self._l1_manager
|
|
300
|
+
|
|
301
|
+
for adapter_index, target_keys in plan.items():
|
|
302
|
+
if not target_keys:
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
if adapter_index >= len(self._l2_adapters):
|
|
306
|
+
logger.error(
|
|
307
|
+
"StorePolicy returned invalid adapter index %d "
|
|
308
|
+
"(only %d adapters available). Skipping.",
|
|
309
|
+
adapter_index,
|
|
310
|
+
len(self._l2_adapters),
|
|
311
|
+
)
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
# Reserve read to get MemoryObj references and hold read locks
|
|
315
|
+
read_results = l1_mgr.reserve_read(target_keys)
|
|
316
|
+
|
|
317
|
+
successful_keys = []
|
|
318
|
+
successful_objs = []
|
|
319
|
+
for key in target_keys:
|
|
320
|
+
result = read_results.get(key)
|
|
321
|
+
if result is None:
|
|
322
|
+
continue
|
|
323
|
+
err, obj = result
|
|
324
|
+
if err != L1Error.SUCCESS or obj is None:
|
|
325
|
+
logger.debug(
|
|
326
|
+
"Skipping key %s for L2 store (adapter %d): %s",
|
|
327
|
+
key,
|
|
328
|
+
adapter_index,
|
|
329
|
+
err,
|
|
330
|
+
)
|
|
331
|
+
continue
|
|
332
|
+
successful_keys.append(key)
|
|
333
|
+
successful_objs.append(obj)
|
|
334
|
+
|
|
335
|
+
if not successful_keys:
|
|
336
|
+
continue
|
|
337
|
+
|
|
338
|
+
adapter = self._l2_adapters[adapter_index]
|
|
339
|
+
task_id = adapter.submit_store_task(successful_keys, successful_objs)
|
|
340
|
+
|
|
341
|
+
self._in_flight_tasks[(adapter_index, task_id)] = InFlightStoreTask(
|
|
342
|
+
adapter_index=adapter_index,
|
|
343
|
+
keys=successful_keys,
|
|
344
|
+
read_locked_keys=list(successful_keys),
|
|
345
|
+
)
|
|
346
|
+
self._status_in_flight_count += 1
|
|
347
|
+
|
|
348
|
+
self._event_bus.publish(
|
|
349
|
+
Event(
|
|
350
|
+
event_type=EventType.L2_STORE_SUBMITTED,
|
|
351
|
+
metadata={
|
|
352
|
+
"adapter_index": adapter_index,
|
|
353
|
+
"key_count": len(successful_keys),
|
|
354
|
+
},
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
logger.debug(
|
|
359
|
+
"Submitted store task %d to adapter %d with %d keys.",
|
|
360
|
+
task_id,
|
|
361
|
+
adapter_index,
|
|
362
|
+
len(successful_keys),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def _process_completed_tasks(self, adapter_index: int) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Process completed store tasks for a given adapter.
|
|
368
|
+
|
|
369
|
+
1. Pop all completed tasks from the adapter.
|
|
370
|
+
2. Release L1 read locks for each task.
|
|
371
|
+
3. If the task succeeded, ask the policy which keys to
|
|
372
|
+
delete from L1 and delete them.
|
|
373
|
+
4. Remove the task from in-flight tracking.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
adapter_index (int): Index of the adapter whose eventfd
|
|
377
|
+
was signaled.
|
|
378
|
+
"""
|
|
379
|
+
adapter = self._l2_adapters[adapter_index]
|
|
380
|
+
completed = adapter.pop_completed_store_tasks()
|
|
381
|
+
|
|
382
|
+
l1_mgr = self._l1_manager
|
|
383
|
+
|
|
384
|
+
for task_id, success in completed.items():
|
|
385
|
+
composite_key = (adapter_index, task_id)
|
|
386
|
+
task = self._in_flight_tasks.pop(composite_key, None)
|
|
387
|
+
if task is not None:
|
|
388
|
+
self._status_in_flight_count -= 1
|
|
389
|
+
if task is None:
|
|
390
|
+
logger.warning(
|
|
391
|
+
"Completed store task %d (adapter %d) not found in tracking.",
|
|
392
|
+
task_id,
|
|
393
|
+
adapter_index,
|
|
394
|
+
)
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
# Always release read locks
|
|
398
|
+
l1_mgr.finish_read(task.read_locked_keys)
|
|
399
|
+
|
|
400
|
+
if success:
|
|
401
|
+
self._event_bus.publish(
|
|
402
|
+
Event(
|
|
403
|
+
event_type=EventType.L2_STORE_COMPLETED,
|
|
404
|
+
metadata={
|
|
405
|
+
"adapter_index": adapter_index,
|
|
406
|
+
"succeeded_count": len(task.keys),
|
|
407
|
+
"failed_count": 0,
|
|
408
|
+
},
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
logger.debug(
|
|
412
|
+
"L2 store task %d completed: adapter %d, %d keys.",
|
|
413
|
+
task_id,
|
|
414
|
+
adapter_index,
|
|
415
|
+
len(task.keys),
|
|
416
|
+
)
|
|
417
|
+
delete_keys = self._policy.select_l1_deletions(task.keys)
|
|
418
|
+
if delete_keys:
|
|
419
|
+
l1_mgr.delete(delete_keys)
|
|
420
|
+
else:
|
|
421
|
+
self._event_bus.publish(
|
|
422
|
+
Event(
|
|
423
|
+
event_type=EventType.L2_STORE_COMPLETED,
|
|
424
|
+
metadata={
|
|
425
|
+
"adapter_index": adapter_index,
|
|
426
|
+
"succeeded_count": 0,
|
|
427
|
+
"failed_count": len(task.keys),
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
logger.warning(
|
|
432
|
+
"Store task %d to adapter %d failed for keys: %s",
|
|
433
|
+
task_id,
|
|
434
|
+
adapter_index,
|
|
435
|
+
task.keys,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
def _cleanup_in_flight_tasks(self) -> None:
|
|
439
|
+
"""
|
|
440
|
+
Release all held read locks for any in-flight tasks that
|
|
441
|
+
haven't completed. Called during stop().
|
|
442
|
+
"""
|
|
443
|
+
l1_mgr = self._l1_manager
|
|
444
|
+
for (adapter_index, task_id), task in self._in_flight_tasks.items():
|
|
445
|
+
logger.warning(
|
|
446
|
+
"Cleaning up in-flight store task %d (adapter %d, %d keys).",
|
|
447
|
+
task_id,
|
|
448
|
+
adapter_index,
|
|
449
|
+
len(task.read_locked_keys),
|
|
450
|
+
)
|
|
451
|
+
l1_mgr.finish_read(task.read_locked_keys)
|
|
452
|
+
self._in_flight_tasks.clear()
|