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,679 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Callable, NamedTuple, Optional, Set
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
# Third Party
|
|
9
|
+
import zmq.asyncio
|
|
10
|
+
|
|
11
|
+
# First Party
|
|
12
|
+
from lmcache.logging import init_logger
|
|
13
|
+
from lmcache.v1.cache_controller.locks import FastLockWithTimeout, RWLockWithTimeout
|
|
14
|
+
from lmcache.v1.cache_controller.message import BatchedKVOperationMsg, WorkerInfo
|
|
15
|
+
from lmcache.v1.utils.cache_utils import TTLListCache
|
|
16
|
+
|
|
17
|
+
logger = init_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KVChunkInfo(NamedTuple):
|
|
21
|
+
"""
|
|
22
|
+
Represents the location information of a KV chunk in the cluster.
|
|
23
|
+
This class is immutable and can be used as a dictionary key.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
instance_id: str
|
|
27
|
+
worker_id: int
|
|
28
|
+
location: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FullSyncState(Enum):
|
|
32
|
+
"""State of full sync for a worker"""
|
|
33
|
+
|
|
34
|
+
IDLE = "idle" # Not in sync
|
|
35
|
+
SYNCING = "syncing" # Sync in progress
|
|
36
|
+
COMPLETED = "completed" # Sync completed
|
|
37
|
+
FAILED = "failed" # Sync failed/timeout
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class WorkerSyncInfo:
|
|
42
|
+
"""Information about a worker's sync state"""
|
|
43
|
+
|
|
44
|
+
sync_id: str
|
|
45
|
+
state: FullSyncState
|
|
46
|
+
start_time: float
|
|
47
|
+
expected_total_keys: int
|
|
48
|
+
expected_batch_count: int
|
|
49
|
+
received_batches: Set[int] = field(default_factory=set)
|
|
50
|
+
received_keys_count: int = 0
|
|
51
|
+
last_activity_time: float = 0.0
|
|
52
|
+
|
|
53
|
+
def __post_init__(self):
|
|
54
|
+
if self.last_activity_time == 0.0:
|
|
55
|
+
self.last_activity_time = self.start_time
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class WorkerNode:
|
|
60
|
+
"""
|
|
61
|
+
Represents a single worker with all its associated metadata.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
worker_id: int
|
|
65
|
+
ip: str
|
|
66
|
+
port: int
|
|
67
|
+
peer_init_url: Optional[str]
|
|
68
|
+
socket: Optional[zmq.asyncio.Socket]
|
|
69
|
+
registration_time: float
|
|
70
|
+
last_heartbeat_time: float
|
|
71
|
+
# Guarded by _lock
|
|
72
|
+
seq_tracker: dict[str, int] = field(default_factory=dict) # location -> seq_num
|
|
73
|
+
kv_store: dict[str, set[int]] = field(
|
|
74
|
+
default_factory=dict
|
|
75
|
+
) # location -> set[chunk_hash]
|
|
76
|
+
sync_info: Optional[WorkerSyncInfo] = None # Full sync state
|
|
77
|
+
|
|
78
|
+
def __post_init__(self):
|
|
79
|
+
# Fast lock with timeout for WorkerNode operations
|
|
80
|
+
self._lock = FastLockWithTimeout()
|
|
81
|
+
|
|
82
|
+
def handle_batched_kv_operations(
|
|
83
|
+
self,
|
|
84
|
+
msg: BatchedKVOperationMsg,
|
|
85
|
+
on_seq_num_out_of_order: Optional[Callable[[], None]] = None,
|
|
86
|
+
is_full_sync: bool = False,
|
|
87
|
+
) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Handle batched KV operations with single lock acquisition.
|
|
90
|
+
Logs warning and calls callback if sequence out of order is detected.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
msg: The batched KV operation message.
|
|
94
|
+
on_seq_num_out_of_order: Callback when sequence out of order detected.
|
|
95
|
+
is_full_sync: If True, this is a full sync batch operation.
|
|
96
|
+
During full sync, sequence check is skipped.
|
|
97
|
+
When worker is syncing, non-full-sync operations are rejected.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if operations were processed, False if rejected
|
|
101
|
+
(e.g., during syncing when is_full_sync=False).
|
|
102
|
+
"""
|
|
103
|
+
seq_warning: Optional[tuple[int, int, int]] = None
|
|
104
|
+
with self._lock:
|
|
105
|
+
# During syncing, reject non-full-sync operations
|
|
106
|
+
if (
|
|
107
|
+
self.sync_info is not None
|
|
108
|
+
and self.sync_info.state == FullSyncState.SYNCING
|
|
109
|
+
and not is_full_sync
|
|
110
|
+
):
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
location = msg.location
|
|
114
|
+
|
|
115
|
+
if location not in self.kv_store:
|
|
116
|
+
self.kv_store[location] = set()
|
|
117
|
+
|
|
118
|
+
for op in msg.operations:
|
|
119
|
+
# Apply operation first, then check sequence
|
|
120
|
+
# (sequence check is skipped during full sync)
|
|
121
|
+
if op.op_type.value == "admit":
|
|
122
|
+
self.kv_store[location].add(op.key)
|
|
123
|
+
elif op.op_type.value == "evict":
|
|
124
|
+
self.kv_store[location].discard(op.key)
|
|
125
|
+
else:
|
|
126
|
+
logger.error(f"Unknown op_type: {op.op_type}")
|
|
127
|
+
|
|
128
|
+
# Skip sequence check during full sync
|
|
129
|
+
if is_full_sync:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Sequence check
|
|
133
|
+
last_seq_num = self.seq_tracker.get(location)
|
|
134
|
+
if last_seq_num is not None:
|
|
135
|
+
expected_seq = last_seq_num + 1
|
|
136
|
+
if op.seq_num != expected_seq and seq_warning is None:
|
|
137
|
+
seq_warning = (
|
|
138
|
+
expected_seq,
|
|
139
|
+
op.seq_num,
|
|
140
|
+
op.seq_num - expected_seq,
|
|
141
|
+
)
|
|
142
|
+
self.seq_tracker[location] = op.seq_num
|
|
143
|
+
|
|
144
|
+
# Clean up empty set
|
|
145
|
+
if not self.kv_store[location]:
|
|
146
|
+
self.kv_store.pop(location, None)
|
|
147
|
+
|
|
148
|
+
# Log warning and call callback outside lock
|
|
149
|
+
if seq_warning is not None:
|
|
150
|
+
if on_seq_num_out_of_order is not None:
|
|
151
|
+
on_seq_num_out_of_order()
|
|
152
|
+
logger.warning(
|
|
153
|
+
"KV batch sequence out of order detected: "
|
|
154
|
+
"key=%s, expected_seq=%s, actual_seq=%s, gap=%s",
|
|
155
|
+
(msg.instance_id, msg.worker_id, msg.location),
|
|
156
|
+
seq_warning[0],
|
|
157
|
+
seq_warning[1],
|
|
158
|
+
seq_warning[2],
|
|
159
|
+
)
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
def has_kv(self, location: str, key: int) -> bool:
|
|
163
|
+
"""Check if a KV chunk exists in this worker."""
|
|
164
|
+
with self._lock:
|
|
165
|
+
return location in self.kv_store and key in self.kv_store[location]
|
|
166
|
+
|
|
167
|
+
def get_kv_keys(self, location: str) -> set[int]:
|
|
168
|
+
"""Get all keys for a location."""
|
|
169
|
+
with self._lock:
|
|
170
|
+
keys = self.kv_store.get(location)
|
|
171
|
+
if keys is None:
|
|
172
|
+
return set()
|
|
173
|
+
# Return a shallow copy for thread safety
|
|
174
|
+
return set(keys)
|
|
175
|
+
|
|
176
|
+
def clear_kv_store(self) -> None:
|
|
177
|
+
"""Clear all KV data for this worker."""
|
|
178
|
+
with self._lock:
|
|
179
|
+
self.kv_store.clear()
|
|
180
|
+
self.seq_tracker.clear()
|
|
181
|
+
|
|
182
|
+
def get_kv_count(self) -> int:
|
|
183
|
+
"""Get total count of KV chunks."""
|
|
184
|
+
with self._lock:
|
|
185
|
+
return sum(len(keys) for keys in self.kv_store.values())
|
|
186
|
+
|
|
187
|
+
def update_seq_num(self, location: str, seq_num: int) -> None:
|
|
188
|
+
"""Update sequence number for a location."""
|
|
189
|
+
with self._lock:
|
|
190
|
+
self.seq_tracker[location] = seq_num
|
|
191
|
+
|
|
192
|
+
def get_seq_num(self, location: str) -> Optional[int]:
|
|
193
|
+
"""Get sequence number for a location."""
|
|
194
|
+
with self._lock:
|
|
195
|
+
return self.seq_tracker.get(location)
|
|
196
|
+
|
|
197
|
+
def find_key(
|
|
198
|
+
self, key: int
|
|
199
|
+
) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
|
|
200
|
+
"""
|
|
201
|
+
Find a key in this worker's kv_store.
|
|
202
|
+
Returns: (KVChunkInfo, peer_init_url, keys) if found, None otherwise.
|
|
203
|
+
KVChunkInfo will have instance_id="" since WorkerNode doesn't know
|
|
204
|
+
its instance.
|
|
205
|
+
"""
|
|
206
|
+
with self._lock:
|
|
207
|
+
for location, keys in self.kv_store.items():
|
|
208
|
+
if key in keys:
|
|
209
|
+
# WorkerNode doesn't know its instance_id, so we leave it empty
|
|
210
|
+
# The caller should fill in the instance_id
|
|
211
|
+
return (
|
|
212
|
+
KVChunkInfo("", self.worker_id, location),
|
|
213
|
+
self.peer_init_url,
|
|
214
|
+
keys,
|
|
215
|
+
)
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def find_key_simple(self, key: int) -> Optional[KVChunkInfo]:
|
|
219
|
+
"""
|
|
220
|
+
Find a key in this worker's kv_store, returning only KVChunkInfo.
|
|
221
|
+
KVChunkInfo will have instance_id="" since WorkerNode doesn't know
|
|
222
|
+
its instance.
|
|
223
|
+
"""
|
|
224
|
+
with self._lock:
|
|
225
|
+
for location, keys in self.kv_store.items():
|
|
226
|
+
if key in keys:
|
|
227
|
+
return KVChunkInfo("", self.worker_id, location)
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
def to_worker_info(self, instance_id: str) -> WorkerInfo:
|
|
231
|
+
"""Convert to WorkerInfo for backward compatibility."""
|
|
232
|
+
# No need to lock here
|
|
233
|
+
return WorkerInfo(
|
|
234
|
+
instance_id=instance_id,
|
|
235
|
+
worker_id=self.worker_id,
|
|
236
|
+
ip=self.ip,
|
|
237
|
+
port=self.port,
|
|
238
|
+
peer_init_url=self.peer_init_url,
|
|
239
|
+
registration_time=self.registration_time,
|
|
240
|
+
last_heartbeat_time=self.last_heartbeat_time,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass
|
|
245
|
+
class InstanceNode:
|
|
246
|
+
"""
|
|
247
|
+
Represents an instance with all its workers.
|
|
248
|
+
Tree structure: InstanceNode -> WorkerNode
|
|
249
|
+
Each InstanceNode has its own lock for thread-safe worker operations.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
instance_id: str
|
|
253
|
+
# Guarded by _rwlock
|
|
254
|
+
workers: dict[int, WorkerNode] = field(
|
|
255
|
+
default_factory=dict
|
|
256
|
+
) # worker_id -> WorkerNode
|
|
257
|
+
|
|
258
|
+
def __post_init__(self):
|
|
259
|
+
# RW lock for protecting workers dict access
|
|
260
|
+
self._rwlock = RWLockWithTimeout()
|
|
261
|
+
|
|
262
|
+
def add_worker(self, worker_node: WorkerNode) -> None:
|
|
263
|
+
"""Add a worker to this instance."""
|
|
264
|
+
with self._rwlock.write_lock(timeout=10):
|
|
265
|
+
self.workers[worker_node.worker_id] = worker_node
|
|
266
|
+
|
|
267
|
+
def remove_worker(self, worker_id: int) -> Optional[WorkerNode]:
|
|
268
|
+
"""Remove and return a worker from this instance."""
|
|
269
|
+
with self._rwlock.write_lock(timeout=10):
|
|
270
|
+
return self.workers.pop(worker_id, None)
|
|
271
|
+
|
|
272
|
+
def get_worker(self, worker_id: int) -> Optional[WorkerNode]:
|
|
273
|
+
"""Get a worker by worker_id. Uses optimistic locking (lock-free read)."""
|
|
274
|
+
# Optimistic read: workers dict rarely changes
|
|
275
|
+
return self.workers.get(worker_id)
|
|
276
|
+
|
|
277
|
+
def get_worker_ids(self) -> list[int]:
|
|
278
|
+
"""Get sorted list of worker IDs."""
|
|
279
|
+
# Snapshot keys to avoid RuntimeError if dict changes during iteration
|
|
280
|
+
return sorted(list(self.workers.keys()))
|
|
281
|
+
|
|
282
|
+
def has_workers(self) -> bool:
|
|
283
|
+
"""Check if instance has any workers."""
|
|
284
|
+
# Optimistic read: workers dict rarely changes
|
|
285
|
+
return len(self.workers) > 0
|
|
286
|
+
|
|
287
|
+
def get_all_worker_infos(self) -> list[WorkerInfo]:
|
|
288
|
+
"""Get WorkerInfo for all workers in this instance."""
|
|
289
|
+
# Snapshot values to avoid RuntimeError if dict changes during iteration
|
|
290
|
+
workers_snapshot = list(self.workers.values())
|
|
291
|
+
return [worker.to_worker_info(self.instance_id) for worker in workers_snapshot]
|
|
292
|
+
|
|
293
|
+
def find_key(
|
|
294
|
+
self, key: int
|
|
295
|
+
) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
|
|
296
|
+
"""
|
|
297
|
+
Find a key in any worker within this instance.
|
|
298
|
+
Returns: (KVChunkInfo, peer_init_url, keys) if found, None otherwise.
|
|
299
|
+
"""
|
|
300
|
+
# Snapshot workers to avoid RuntimeError if dict changes during iteration
|
|
301
|
+
workers_snapshot = list(self.workers.items())
|
|
302
|
+
for worker_id, worker_node in workers_snapshot:
|
|
303
|
+
if result := worker_node.find_key(key):
|
|
304
|
+
# Fill in the instance_id in KVChunkInfo
|
|
305
|
+
kv_info, peer_init_url, keys = result
|
|
306
|
+
return (
|
|
307
|
+
KVChunkInfo(self.instance_id, worker_id, kv_info.location),
|
|
308
|
+
peer_init_url,
|
|
309
|
+
keys,
|
|
310
|
+
)
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
def find_key_simple(self, key: int) -> Optional[KVChunkInfo]:
|
|
314
|
+
"""
|
|
315
|
+
Find a key in any worker within this instance, returning only KVChunkInfo.
|
|
316
|
+
"""
|
|
317
|
+
# Snapshot workers to avoid RuntimeError if dict changes during iteration
|
|
318
|
+
workers_snapshot = list(self.workers.items())
|
|
319
|
+
for worker_id, worker_node in workers_snapshot:
|
|
320
|
+
if kv_info := worker_node.find_key_simple(key):
|
|
321
|
+
return KVChunkInfo(self.instance_id, worker_id, kv_info.location)
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
def has_worker_with_ip(self, ip: str) -> bool:
|
|
325
|
+
"""
|
|
326
|
+
Check if any worker in this instance has the specified IP address.
|
|
327
|
+
"""
|
|
328
|
+
# Snapshot workers to avoid RuntimeError if dict changes during iteration
|
|
329
|
+
return any(worker.ip == ip for worker in list(self.workers.values()))
|
|
330
|
+
|
|
331
|
+
def get_total_kv_count(self) -> int:
|
|
332
|
+
"""
|
|
333
|
+
Get total count of KV chunks across all workers in this instance.
|
|
334
|
+
"""
|
|
335
|
+
# Snapshot workers to avoid RuntimeError if dict changes during iteration
|
|
336
|
+
return sum(worker.get_kv_count() for worker in list(self.workers.values()))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class RegistryTree:
|
|
340
|
+
"""
|
|
341
|
+
Central registry managing the tree structure of instances and workers.
|
|
342
|
+
Structure: instance_id -> InstanceNode -> WorkerNode
|
|
343
|
+
|
|
344
|
+
Lock hierarchy (from coarse to fine):
|
|
345
|
+
1. RegistryTree._rwlock: protects instances dict access
|
|
346
|
+
2. InstanceNode._rwlock: protects workers dict access
|
|
347
|
+
3. WorkerNode._lock: protects kv_store and seq_tracker access
|
|
348
|
+
|
|
349
|
+
This fine-grained locking allows concurrent operations on different
|
|
350
|
+
instances/workers, improving throughput significantly.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
def __init__(self):
|
|
354
|
+
# Guarded by _rwlock
|
|
355
|
+
# instance_id -> InstanceNode
|
|
356
|
+
self.instances: dict[str, InstanceNode] = {}
|
|
357
|
+
# RW lock only for protecting instances dict access
|
|
358
|
+
self._rwlock = RWLockWithTimeout()
|
|
359
|
+
# Atomic counter for sequence discontinuity (protected by _counter_lock)
|
|
360
|
+
self._seq_discontinuity_count = 0
|
|
361
|
+
self._counter_lock = threading.Lock()
|
|
362
|
+
# Cache for get_all_worker_infos (for Prometheus metrics)
|
|
363
|
+
self._worker_info_cache: TTLListCache[WorkerInfo] = TTLListCache[WorkerInfo]()
|
|
364
|
+
# Cache for get_all_worker_nodes (for FullSyncTracker and other use cases)
|
|
365
|
+
self._worker_node_cache: TTLListCache[tuple[str, WorkerNode]] = TTLListCache[
|
|
366
|
+
tuple[str, WorkerNode]
|
|
367
|
+
]()
|
|
368
|
+
|
|
369
|
+
def get_seq_discontinuity_count(self) -> int:
|
|
370
|
+
"""Get the count of sequence discontinuities (thread-safe)."""
|
|
371
|
+
# Lock-free read, no need for lock
|
|
372
|
+
return self._seq_discontinuity_count
|
|
373
|
+
|
|
374
|
+
def _incr_seq_discontinuity_count(self) -> None:
|
|
375
|
+
"""Increment the sequence discontinuity counter (thread-safe)."""
|
|
376
|
+
with self._counter_lock:
|
|
377
|
+
self._seq_discontinuity_count += 1
|
|
378
|
+
|
|
379
|
+
def _get_or_create_instance(self, instance_id: str) -> InstanceNode:
|
|
380
|
+
"""Get or create an instance node. Internal use only."""
|
|
381
|
+
# Optimistic read first: instances dict rarely changes
|
|
382
|
+
instance_node = self.instances.get(instance_id)
|
|
383
|
+
if instance_node is not None:
|
|
384
|
+
return instance_node
|
|
385
|
+
|
|
386
|
+
# Need to create, use write lock
|
|
387
|
+
with self._rwlock.write_lock(timeout=10):
|
|
388
|
+
# Double-check after acquiring write lock
|
|
389
|
+
instance_node = self.instances.get(instance_id)
|
|
390
|
+
if instance_node is None:
|
|
391
|
+
instance_node = InstanceNode(instance_id=instance_id)
|
|
392
|
+
self.instances[instance_id] = instance_node
|
|
393
|
+
return instance_node
|
|
394
|
+
|
|
395
|
+
def _get_instance(self, instance_id: str) -> Optional[InstanceNode]:
|
|
396
|
+
"""Get an instance node. Uses optimistic locking (lock-free read)."""
|
|
397
|
+
# Optimistic read: instances dict rarely changes
|
|
398
|
+
return self.instances.get(instance_id)
|
|
399
|
+
|
|
400
|
+
def register_worker(
|
|
401
|
+
self,
|
|
402
|
+
instance_id: str,
|
|
403
|
+
worker_id: int,
|
|
404
|
+
ip: str,
|
|
405
|
+
port: int,
|
|
406
|
+
peer_init_url: Optional[str],
|
|
407
|
+
socket: zmq.asyncio.Socket,
|
|
408
|
+
registration_time: float,
|
|
409
|
+
) -> WorkerNode:
|
|
410
|
+
"""Register a new worker, creating instance if needed."""
|
|
411
|
+
# Get or create instance (locks instances dict)
|
|
412
|
+
instance_node = self._get_or_create_instance(instance_id)
|
|
413
|
+
|
|
414
|
+
# Create worker node
|
|
415
|
+
worker_node = WorkerNode(
|
|
416
|
+
worker_id=worker_id,
|
|
417
|
+
ip=ip,
|
|
418
|
+
port=port,
|
|
419
|
+
peer_init_url=peer_init_url,
|
|
420
|
+
socket=socket,
|
|
421
|
+
registration_time=registration_time,
|
|
422
|
+
last_heartbeat_time=registration_time,
|
|
423
|
+
)
|
|
424
|
+
# Add worker (locks workers dict in instance_node)
|
|
425
|
+
instance_node.add_worker(worker_node)
|
|
426
|
+
return worker_node
|
|
427
|
+
|
|
428
|
+
def deregister_worker(
|
|
429
|
+
self, instance_id: str, worker_id: int
|
|
430
|
+
) -> Optional[WorkerNode]:
|
|
431
|
+
"""Deregister a worker and clean up empty instances."""
|
|
432
|
+
instance_node = self._get_instance(instance_id)
|
|
433
|
+
if instance_node is None:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
# Remove worker (locks workers dict in instance_node)
|
|
437
|
+
worker_node = instance_node.remove_worker(worker_id)
|
|
438
|
+
|
|
439
|
+
# Clean up empty instance (need write lock on instances)
|
|
440
|
+
if not instance_node.has_workers():
|
|
441
|
+
# TODO(baoloongmao): Move timeout values to configuration
|
|
442
|
+
with self._rwlock.write_lock(timeout=100):
|
|
443
|
+
# Double-check after acquiring write lock
|
|
444
|
+
# Use pop to avoid KeyError if another thread already removed it
|
|
445
|
+
if not instance_node.has_workers() and instance_id in self.instances:
|
|
446
|
+
self.instances.pop(instance_id, None)
|
|
447
|
+
|
|
448
|
+
return worker_node
|
|
449
|
+
|
|
450
|
+
def get_worker(self, instance_id: str, worker_id: int) -> Optional[WorkerNode]:
|
|
451
|
+
"""Get a specific worker."""
|
|
452
|
+
instance_node = self._get_instance(instance_id)
|
|
453
|
+
if instance_node is None:
|
|
454
|
+
return None
|
|
455
|
+
return instance_node.get_worker(worker_id)
|
|
456
|
+
|
|
457
|
+
def get_instance(self, instance_id: str) -> Optional[InstanceNode]:
|
|
458
|
+
"""Get an instance by instance_id."""
|
|
459
|
+
return self._get_instance(instance_id)
|
|
460
|
+
|
|
461
|
+
def get_instance_by_ip(self, ip: str) -> Optional[InstanceNode]:
|
|
462
|
+
"""Get an instance by IP address. Returns first instance if multiple exist."""
|
|
463
|
+
# Snapshot to avoid RuntimeError if dict changes during iteration
|
|
464
|
+
for instance_node in list(self.instances.values()):
|
|
465
|
+
if instance_node.has_worker_with_ip(ip):
|
|
466
|
+
return instance_node
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
def get_instances_by_ip(self, ip: str) -> list[InstanceNode]:
|
|
470
|
+
"""Get all instances by IP address."""
|
|
471
|
+
# Snapshot to avoid RuntimeError if dict changes during iteration
|
|
472
|
+
result = []
|
|
473
|
+
for instance_node in list(self.instances.values()):
|
|
474
|
+
if instance_node.has_worker_with_ip(ip):
|
|
475
|
+
result.append(instance_node)
|
|
476
|
+
return result
|
|
477
|
+
|
|
478
|
+
def get_worker_ids(self, instance_id: str) -> list[int]:
|
|
479
|
+
"""Get sorted list of worker IDs for an instance."""
|
|
480
|
+
instance_node = self._get_instance(instance_id)
|
|
481
|
+
if instance_node is None:
|
|
482
|
+
return []
|
|
483
|
+
return instance_node.get_worker_ids()
|
|
484
|
+
|
|
485
|
+
def get_all_worker_infos_cached(
|
|
486
|
+
self, timeout_seconds: Optional[float] = None
|
|
487
|
+
) -> list[WorkerInfo]:
|
|
488
|
+
"""Get WorkerInfo for all workers across all instances.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
timeout_seconds: Cache timeout in seconds.
|
|
492
|
+
- If None (default): use the cache's default timeout.
|
|
493
|
+
- If 0: cache immediately expires, always get fresh data
|
|
494
|
+
and update cache.
|
|
495
|
+
- If > 0: use cache with specified timeout.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
List of WorkerInfo, either from cache or fresh data
|
|
499
|
+
"""
|
|
500
|
+
return self._worker_info_cache.get_cached(
|
|
501
|
+
lambda: self._get_all_worker_infos_fresh(), timeout_override=timeout_seconds
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
def _get_all_worker_infos_fresh(self) -> list[WorkerInfo]:
|
|
505
|
+
"""Internal method to get fresh WorkerInfo without cache."""
|
|
506
|
+
# Snapshot to avoid RuntimeError if dict changes during iteration
|
|
507
|
+
result = []
|
|
508
|
+
for instance_node in list(self.instances.values()):
|
|
509
|
+
result.extend(instance_node.get_all_worker_infos())
|
|
510
|
+
return result
|
|
511
|
+
|
|
512
|
+
def get_all_worker_nodes_cached(
|
|
513
|
+
self, timeout_seconds: Optional[float] = None
|
|
514
|
+
) -> list[tuple[str, WorkerNode]]:
|
|
515
|
+
"""Get all (instance_id, WorkerNode) tuples across all instances, with caching.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
timeout_seconds: Cache timeout in seconds.
|
|
519
|
+
- If None (default): use the cache's default timeout.
|
|
520
|
+
- If 0: cache immediately expires, always get fresh data
|
|
521
|
+
and update cache.
|
|
522
|
+
- If > 0: use cache with specified timeout.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
List of (instance_id, WorkerNode) tuples, either from cache or fresh data
|
|
526
|
+
"""
|
|
527
|
+
return self._worker_node_cache.get_cached(
|
|
528
|
+
lambda: self._get_all_worker_nodes_fresh(), timeout_override=timeout_seconds
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
def _get_all_worker_nodes_fresh(self) -> list[tuple[str, WorkerNode]]:
|
|
532
|
+
"""Internal method to get fresh (instance_id, WorkerNode) tuples
|
|
533
|
+
without cache."""
|
|
534
|
+
# Snapshot to avoid RuntimeError if dict changes during iteration
|
|
535
|
+
result = []
|
|
536
|
+
for instance_id, instance_node in self.instances.items():
|
|
537
|
+
for worker_node in instance_node.workers.values():
|
|
538
|
+
result.append((instance_id, worker_node))
|
|
539
|
+
return result
|
|
540
|
+
|
|
541
|
+
def update_heartbeat(
|
|
542
|
+
self, instance_id: str, worker_id: int, timestamp: float
|
|
543
|
+
) -> bool:
|
|
544
|
+
"""Update worker heartbeat timestamp. Returns True if successful."""
|
|
545
|
+
instance_node = self._get_instance(instance_id)
|
|
546
|
+
if instance_node is None:
|
|
547
|
+
return False
|
|
548
|
+
worker_node = instance_node.get_worker(worker_id)
|
|
549
|
+
if worker_node is None:
|
|
550
|
+
return False
|
|
551
|
+
worker_node.last_heartbeat_time = timestamp
|
|
552
|
+
return True
|
|
553
|
+
|
|
554
|
+
def handle_batched_kv_operations(
|
|
555
|
+
self, msg: BatchedKVOperationMsg, is_full_sync: bool = False
|
|
556
|
+
) -> bool:
|
|
557
|
+
"""
|
|
558
|
+
Handle batched KV operations by forwarding to WorkerNode.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
msg: The batched KV operation message.
|
|
562
|
+
is_full_sync: If True, this is a full sync batch operation.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
True if operations were processed successfully.
|
|
566
|
+
False if worker not found or operations were rejected.
|
|
567
|
+
"""
|
|
568
|
+
instance_node = self._get_instance(msg.instance_id)
|
|
569
|
+
if instance_node is None:
|
|
570
|
+
return False
|
|
571
|
+
worker_node = instance_node.get_worker(msg.worker_id)
|
|
572
|
+
if worker_node is None:
|
|
573
|
+
return False
|
|
574
|
+
return worker_node.handle_batched_kv_operations(
|
|
575
|
+
msg,
|
|
576
|
+
on_seq_num_out_of_order=self._incr_seq_discontinuity_count,
|
|
577
|
+
is_full_sync=is_full_sync,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
def find_kv(
|
|
581
|
+
self,
|
|
582
|
+
key: int,
|
|
583
|
+
exclude_instance_id: Optional[str] = None,
|
|
584
|
+
) -> Optional[KVChunkInfo]:
|
|
585
|
+
"""
|
|
586
|
+
Find a KV chunk across all workers.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
key: The KV chunk key to find.
|
|
590
|
+
exclude_instance_id: Instance ID to exclude
|
|
591
|
+
(all workers in this instance will be excluded).
|
|
592
|
+
|
|
593
|
+
Returns: KVChunkInfo if found, None otherwise.
|
|
594
|
+
"""
|
|
595
|
+
with self._rwlock.read_lock(timeout=1):
|
|
596
|
+
for instance_id, instance_node in self.instances.items():
|
|
597
|
+
# Exclude all workers in the specified instance
|
|
598
|
+
if (
|
|
599
|
+
exclude_instance_id is not None
|
|
600
|
+
and instance_id == exclude_instance_id
|
|
601
|
+
):
|
|
602
|
+
continue
|
|
603
|
+
result = instance_node.find_key_simple(key)
|
|
604
|
+
if result is not None:
|
|
605
|
+
return result
|
|
606
|
+
return None
|
|
607
|
+
|
|
608
|
+
def find_kv_with_worker_info(
|
|
609
|
+
self,
|
|
610
|
+
key: int,
|
|
611
|
+
exclude_instance_id: Optional[str] = None,
|
|
612
|
+
) -> Optional[tuple[KVChunkInfo, Optional[str], set[int]]]:
|
|
613
|
+
"""
|
|
614
|
+
Find a KV chunk and return worker info in one lookup.
|
|
615
|
+
Optimized for batched_p2p_lookup to avoid multiple lookups.
|
|
616
|
+
|
|
617
|
+
Returns: (KVChunkInfo, peer_init_url, kv_keys) if found, None otherwise.
|
|
618
|
+
"""
|
|
619
|
+
# Snapshot instances to avoid RuntimeError if dict changes during iteration
|
|
620
|
+
for instance_id, instance_node in list(self.instances.items()):
|
|
621
|
+
if exclude_instance_id is not None and instance_id == exclude_instance_id:
|
|
622
|
+
continue
|
|
623
|
+
result = instance_node.find_key(key)
|
|
624
|
+
if result is not None:
|
|
625
|
+
return result
|
|
626
|
+
return None
|
|
627
|
+
|
|
628
|
+
def get_total_kv_count(self) -> int:
|
|
629
|
+
"""Get total count of KV chunks across all workers."""
|
|
630
|
+
# Snapshot to avoid RuntimeError if dict changes during iteration
|
|
631
|
+
total = 0
|
|
632
|
+
for instance_node in list(self.instances.values()):
|
|
633
|
+
total += instance_node.get_total_kv_count()
|
|
634
|
+
return total
|
|
635
|
+
|
|
636
|
+
def get_worker_kv_keys(
|
|
637
|
+
self, instance_id: str, worker_id: int, location: str
|
|
638
|
+
) -> set[int]:
|
|
639
|
+
"""Get all KV keys for a specific worker and location."""
|
|
640
|
+
instance_node = self._get_instance(instance_id)
|
|
641
|
+
if instance_node is None:
|
|
642
|
+
return set()
|
|
643
|
+
worker_node = instance_node.get_worker(worker_id)
|
|
644
|
+
if worker_node is None:
|
|
645
|
+
return set()
|
|
646
|
+
return worker_node.get_kv_keys(location)
|
|
647
|
+
|
|
648
|
+
def clear_worker_kv(
|
|
649
|
+
self, instance_id: str, worker_id: int, location: Optional[str] = None
|
|
650
|
+
) -> bool:
|
|
651
|
+
"""
|
|
652
|
+
Clear all KV chunks for a specific worker and location.
|
|
653
|
+
Returns True if successful, False if worker not found.
|
|
654
|
+
"""
|
|
655
|
+
worker_node = self.get_worker(instance_id, worker_id)
|
|
656
|
+
if worker_node is None:
|
|
657
|
+
return False
|
|
658
|
+
with worker_node._lock:
|
|
659
|
+
if location is None:
|
|
660
|
+
worker_node.kv_store.clear()
|
|
661
|
+
worker_node.seq_tracker.clear()
|
|
662
|
+
return True
|
|
663
|
+
if location in worker_node.kv_store:
|
|
664
|
+
del worker_node.kv_store[location]
|
|
665
|
+
if location in worker_node.seq_tracker:
|
|
666
|
+
del worker_node.seq_tracker[location]
|
|
667
|
+
return True
|
|
668
|
+
|
|
669
|
+
def clear_all_worker_kv(self, instance_id: str, worker_id: int) -> bool:
|
|
670
|
+
"""
|
|
671
|
+
Clear all KV chunks for a worker across all locations.
|
|
672
|
+
Returns True if successful, False if worker not found.
|
|
673
|
+
"""
|
|
674
|
+
worker_node = self.get_worker(instance_id, worker_id)
|
|
675
|
+
if worker_node is None:
|
|
676
|
+
return False
|
|
677
|
+
|
|
678
|
+
worker_node.clear_kv_store()
|
|
679
|
+
return True
|