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,732 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Callable, Generic, Optional, TypeVar, get_type_hints
|
|
6
|
+
import inspect
|
|
7
|
+
import os
|
|
8
|
+
import queue
|
|
9
|
+
import threading
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
# Third Party
|
|
13
|
+
import msgspec
|
|
14
|
+
import zmq
|
|
15
|
+
|
|
16
|
+
# First Party
|
|
17
|
+
from lmcache.logging import init_logger
|
|
18
|
+
from lmcache.v1.multiprocess.affinity_pool import AffinityThreadPool
|
|
19
|
+
from lmcache.v1.multiprocess.custom_types import (
|
|
20
|
+
CudaIPCWrapper,
|
|
21
|
+
get_customized_decoder,
|
|
22
|
+
get_customized_encoder,
|
|
23
|
+
)
|
|
24
|
+
from lmcache.v1.multiprocess.futures import (
|
|
25
|
+
MessagingFuture,
|
|
26
|
+
)
|
|
27
|
+
from lmcache.v1.multiprocess.protocol import (
|
|
28
|
+
HandlerType,
|
|
29
|
+
RequestType,
|
|
30
|
+
get_payload_classes,
|
|
31
|
+
get_response_class,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = init_logger(__name__)
|
|
35
|
+
|
|
36
|
+
T = TypeVar("T")
|
|
37
|
+
|
|
38
|
+
# Internal type used for the client-server communication
|
|
39
|
+
RequestUID = int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Helper functions
|
|
43
|
+
def encode_request_uid(uid: RequestUID) -> bytes:
|
|
44
|
+
return msgspec.msgpack.encode(uid)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def decode_request_uid(b_uid: bytes) -> RequestUID:
|
|
48
|
+
return msgspec.msgpack.decode(b_uid, type=RequestUID)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def unwrap_request_payloads(
|
|
52
|
+
b_payloads: list[bytes], payload_clss: list[Any]
|
|
53
|
+
) -> list[Any]:
|
|
54
|
+
if len(b_payloads) != len(payload_clss):
|
|
55
|
+
raise ValueError("Payload count does not match expected count")
|
|
56
|
+
|
|
57
|
+
decoded_payloads = [
|
|
58
|
+
msgspec_decode(payload, cls=cls)
|
|
59
|
+
for payload, cls in zip(b_payloads, payload_clss, strict=False)
|
|
60
|
+
]
|
|
61
|
+
return decoded_payloads
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def prepare_internal_push_pull_sockets(
|
|
65
|
+
ctx: zmq.Context,
|
|
66
|
+
) -> tuple[zmq.Socket, zmq.Socket]:
|
|
67
|
+
"""Create 2 inproc socket pair for the zmq-poller compatible task
|
|
68
|
+
queue
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
tuple[zmq.Socket, zmq.Socket]: The (push_socket, pull_socket)
|
|
72
|
+
"""
|
|
73
|
+
inproc_url = "inproc://mq_internal_push_pull/" + str(uuid.uuid4())
|
|
74
|
+
push_socket = ctx.socket(zmq.PUSH)
|
|
75
|
+
pull_socket = ctx.socket(zmq.PULL)
|
|
76
|
+
pull_socket.bind(inproc_url)
|
|
77
|
+
push_socket.connect(inproc_url)
|
|
78
|
+
return push_socket, pull_socket
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
_SPECIAL_ENCODER_DECODERS = {
|
|
82
|
+
CudaIPCWrapper: (
|
|
83
|
+
get_customized_encoder(CudaIPCWrapper),
|
|
84
|
+
get_customized_decoder(CudaIPCWrapper),
|
|
85
|
+
),
|
|
86
|
+
list[CudaIPCWrapper]: (
|
|
87
|
+
get_customized_encoder(list[CudaIPCWrapper]),
|
|
88
|
+
get_customized_decoder(list[CudaIPCWrapper]),
|
|
89
|
+
),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def msgspec_encode(obj: Any, cls: Any) -> bytes:
|
|
94
|
+
# Handle special cases
|
|
95
|
+
if cls in _SPECIAL_ENCODER_DECODERS:
|
|
96
|
+
encoder, _ = _SPECIAL_ENCODER_DECODERS[cls]
|
|
97
|
+
return encoder.encode(obj)
|
|
98
|
+
return msgspec.msgpack.encode(obj)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def msgspec_decode(b_obj: bytes, cls: Any) -> Any:
|
|
102
|
+
# Handle special cases
|
|
103
|
+
if cls in _SPECIAL_ENCODER_DECODERS:
|
|
104
|
+
_, decoder = _SPECIAL_ENCODER_DECODERS[cls]
|
|
105
|
+
return decoder.decode(b_obj)
|
|
106
|
+
return msgspec.msgpack.decode(b_obj, type=cls)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Main classes
|
|
110
|
+
class MessageQueueClient:
|
|
111
|
+
@dataclass
|
|
112
|
+
class WrappedRequest:
|
|
113
|
+
request_uid: RequestUID
|
|
114
|
+
future: MessagingFuture[Any]
|
|
115
|
+
request_type: RequestType
|
|
116
|
+
request_payloads: list[Any]
|
|
117
|
+
|
|
118
|
+
def __init__(self, server_url: str, context: zmq.Context):
|
|
119
|
+
# Socket
|
|
120
|
+
self.ctx = context
|
|
121
|
+
self.socket = self.ctx.socket(zmq.DEALER)
|
|
122
|
+
self.socket.connect(server_url)
|
|
123
|
+
|
|
124
|
+
# Input queue
|
|
125
|
+
self.task_notifier, self.task_waiter = prepare_internal_push_pull_sockets(
|
|
126
|
+
self.ctx
|
|
127
|
+
)
|
|
128
|
+
self.input_queue: queue.Queue = queue.Queue()
|
|
129
|
+
|
|
130
|
+
# Poller
|
|
131
|
+
self.poller = zmq.Poller()
|
|
132
|
+
self.poller.register(self.socket, zmq.POLLIN)
|
|
133
|
+
self.poller.register(self.task_waiter, zmq.POLLIN)
|
|
134
|
+
|
|
135
|
+
# main thread
|
|
136
|
+
self.is_finished = threading.Event()
|
|
137
|
+
self.worker_thread = threading.Thread(
|
|
138
|
+
target=self._main_loop, daemon=True, name="mq-client-thread"
|
|
139
|
+
)
|
|
140
|
+
self.worker_thread.start()
|
|
141
|
+
|
|
142
|
+
# Pending job's futures
|
|
143
|
+
self.request_counter = 0
|
|
144
|
+
self.pending_futures: dict[int, MessagingFuture[Any]] = {}
|
|
145
|
+
|
|
146
|
+
def _process_outbound_task(self):
|
|
147
|
+
try:
|
|
148
|
+
while wrapped_request := self.input_queue.get_nowait():
|
|
149
|
+
# wrapped_request = self.input_queue.get_nowait()
|
|
150
|
+
|
|
151
|
+
# Update the pending futures
|
|
152
|
+
request_uid = wrapped_request.request_uid
|
|
153
|
+
self.pending_futures[request_uid] = wrapped_request.future
|
|
154
|
+
|
|
155
|
+
# Send the request
|
|
156
|
+
b_request_uid = msgspec_encode(request_uid, cls=RequestUID)
|
|
157
|
+
b_request_type = msgspec_encode(
|
|
158
|
+
wrapped_request.request_type, cls=RequestType
|
|
159
|
+
)
|
|
160
|
+
payload_classes = get_payload_classes(wrapped_request.request_type)
|
|
161
|
+
if len(payload_classes) != len(wrapped_request.request_payloads):
|
|
162
|
+
raise ValueError("Payload count does not match expected count")
|
|
163
|
+
|
|
164
|
+
b_payloads = [
|
|
165
|
+
msgspec_encode(payload, cls=cls)
|
|
166
|
+
for payload, cls in zip(
|
|
167
|
+
wrapped_request.request_payloads,
|
|
168
|
+
payload_classes,
|
|
169
|
+
strict=False,
|
|
170
|
+
)
|
|
171
|
+
]
|
|
172
|
+
self.socket.send_multipart([b_request_uid, b_request_type] + b_payloads)
|
|
173
|
+
except queue.Empty:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
def _main_loop(self):
|
|
177
|
+
# NOTE: make sure we only edit the pending_futures dict in this thread
|
|
178
|
+
while not self.is_finished.is_set():
|
|
179
|
+
socks = dict(self.poller.poll(1000))
|
|
180
|
+
inbound_state = socks.get(self.socket, None)
|
|
181
|
+
outbound_state = socks.get(self.task_waiter, None)
|
|
182
|
+
|
|
183
|
+
if outbound_state and outbound_state & zmq.POLLIN:
|
|
184
|
+
# Drain the notifier
|
|
185
|
+
while True:
|
|
186
|
+
try:
|
|
187
|
+
self.task_waiter.recv(zmq.DONTWAIT)
|
|
188
|
+
except zmq.Again:
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
# Process the output tasks
|
|
192
|
+
self._process_outbound_task()
|
|
193
|
+
|
|
194
|
+
if inbound_state and inbound_state & zmq.POLLIN:
|
|
195
|
+
msg = self.socket.recv_multipart()
|
|
196
|
+
assert len(msg) >= 2, (
|
|
197
|
+
"Expected at least 2 message part "
|
|
198
|
+
"[request_uid, request_type, *response]"
|
|
199
|
+
)
|
|
200
|
+
b_request_uid, b_request_type, *b_response = msg
|
|
201
|
+
request_uid = msgspec_decode(b_request_uid, cls=RequestUID)
|
|
202
|
+
request_type = msgspec_decode(b_request_type, cls=RequestType)
|
|
203
|
+
response_cls = get_response_class(request_type)
|
|
204
|
+
|
|
205
|
+
if request_uid in self.pending_futures:
|
|
206
|
+
future = self.pending_futures.pop(request_uid)
|
|
207
|
+
if b_response:
|
|
208
|
+
response = msgspec_decode(b_response[0], cls=response_cls)
|
|
209
|
+
future.set_result(response)
|
|
210
|
+
else:
|
|
211
|
+
future.set_result(None)
|
|
212
|
+
|
|
213
|
+
def submit_request(
|
|
214
|
+
self,
|
|
215
|
+
request_type: RequestType,
|
|
216
|
+
request_payloads: list[Any],
|
|
217
|
+
response_cls: Optional[T] = None,
|
|
218
|
+
) -> MessagingFuture[T]:
|
|
219
|
+
"""Submit a request to the server.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
request_type (RequestType): The type of the request.
|
|
223
|
+
request_payloads (list[Any]): The payloads of the request.
|
|
224
|
+
response_cls (Optional[T]): The expected response class.
|
|
225
|
+
This should be get from `get_response_class(request_type)`.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
MessagingFuture[T]: A future that will hold the response.
|
|
229
|
+
"""
|
|
230
|
+
future: MessagingFuture[T] = MessagingFuture()
|
|
231
|
+
request_uid = self.request_counter
|
|
232
|
+
self.request_counter += 1
|
|
233
|
+
self.input_queue.put(
|
|
234
|
+
MessageQueueClient.WrappedRequest(
|
|
235
|
+
request_uid=request_uid,
|
|
236
|
+
future=future,
|
|
237
|
+
request_type=request_type,
|
|
238
|
+
request_payloads=request_payloads,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
self.task_notifier.send(b"1")
|
|
242
|
+
return future
|
|
243
|
+
|
|
244
|
+
def close(self) -> None:
|
|
245
|
+
self.is_finished.set()
|
|
246
|
+
self.worker_thread.join()
|
|
247
|
+
self.socket.close()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
ResponseType = TypeVar("ResponseType", covariant=True)
|
|
251
|
+
StateType = TypeVar("StateType", covariant=True)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class RequestHandlerBase(Generic[ResponseType]):
|
|
255
|
+
def __call__(self, payloads: list[bytes]):
|
|
256
|
+
raise NotImplementedError
|
|
257
|
+
|
|
258
|
+
def get_response_class(self) -> ResponseType:
|
|
259
|
+
raise NotImplementedError
|
|
260
|
+
|
|
261
|
+
def get_handler_type(self) -> HandlerType:
|
|
262
|
+
raise NotImplementedError
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class SyncRequestHandler(RequestHandlerBase[ResponseType]):
|
|
266
|
+
"""
|
|
267
|
+
The handler for those "fast" functions that can be executed in the main loop
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(
|
|
271
|
+
self,
|
|
272
|
+
payload_clss: list[Any],
|
|
273
|
+
response_cls: ResponseType,
|
|
274
|
+
handler: Callable[..., ResponseType],
|
|
275
|
+
):
|
|
276
|
+
self.payload_clss = payload_clss
|
|
277
|
+
self.response_cls = response_cls
|
|
278
|
+
self.handler = handler
|
|
279
|
+
|
|
280
|
+
def __call__(self, payloads: list[bytes]) -> ResponseType:
|
|
281
|
+
return self.handler(*unwrap_request_payloads(payloads, self.payload_clss))
|
|
282
|
+
|
|
283
|
+
def get_response_class(self) -> ResponseType:
|
|
284
|
+
return self.response_cls
|
|
285
|
+
|
|
286
|
+
def get_handler_type(self) -> HandlerType:
|
|
287
|
+
return HandlerType.SYNC
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class BlockingRequestHandler(RequestHandlerBase[ResponseType]):
|
|
291
|
+
"""
|
|
292
|
+
Returns the future of the response.
|
|
293
|
+
|
|
294
|
+
The ``executor`` field is initially ``None`` and must be assigned via
|
|
295
|
+
:meth:`MessageQueueServer.add_normal_thread_pool` or
|
|
296
|
+
:meth:`MessageQueueServer.add_affinity_thread_pool` before the server
|
|
297
|
+
is started.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def __init__(
|
|
301
|
+
self,
|
|
302
|
+
payload_clss: list[Any],
|
|
303
|
+
response_cls: ResponseType,
|
|
304
|
+
handler: Callable[..., ResponseType],
|
|
305
|
+
):
|
|
306
|
+
self.executor: ThreadPoolExecutor | AffinityThreadPool | None = None
|
|
307
|
+
self.payload_clss = payload_clss
|
|
308
|
+
self.handler = handler
|
|
309
|
+
self.response_cls = response_cls
|
|
310
|
+
|
|
311
|
+
def __call__(
|
|
312
|
+
self, payloads: list[bytes], affinity_key: int = 0
|
|
313
|
+
) -> Future[ResponseType]:
|
|
314
|
+
assert self.executor is not None, (
|
|
315
|
+
"BlockingRequestHandler has no executor assigned. "
|
|
316
|
+
"Call add_normal_thread_pool or add_affinity_thread_pool first."
|
|
317
|
+
)
|
|
318
|
+
decoded_payloads = unwrap_request_payloads(payloads, self.payload_clss)
|
|
319
|
+
if isinstance(self.executor, AffinityThreadPool):
|
|
320
|
+
return self.executor.submit(
|
|
321
|
+
self.handler, *decoded_payloads, affinity_key=affinity_key
|
|
322
|
+
)
|
|
323
|
+
return self.executor.submit(self.handler, *decoded_payloads)
|
|
324
|
+
|
|
325
|
+
def get_response_class(self) -> ResponseType:
|
|
326
|
+
return self.response_cls
|
|
327
|
+
|
|
328
|
+
def get_handler_type(self) -> HandlerType:
|
|
329
|
+
return HandlerType.BLOCKING
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class NonBlockingRequestHandler(Generic[ResponseType, StateType]):
|
|
333
|
+
"""
|
|
334
|
+
The handler for the "fire and probe" functions that launch async tasks
|
|
335
|
+
and have special mechanism to probe the task status.
|
|
336
|
+
|
|
337
|
+
It requires 2 callables as the input:
|
|
338
|
+
- the first one is to launch the async task. This function should return
|
|
339
|
+
a 'state handle' that can be used to probe the task status later.
|
|
340
|
+
- the second one is to probe the task status and get the return value
|
|
341
|
+
with the 'state handle' returned by the first function.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
# TODO: implement this in the future versions if needed
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class MessageQueueServer:
|
|
349
|
+
def __init__(self, bind_url: str, context: zmq.Context):
|
|
350
|
+
# Socket
|
|
351
|
+
self.ctx = context
|
|
352
|
+
self.socket = self.ctx.socket(zmq.ROUTER)
|
|
353
|
+
self.socket.bind(bind_url)
|
|
354
|
+
# Use eventfd instead of zmq PUSH/PULL sockets because blocking
|
|
355
|
+
# handler callbacks run on ThreadPoolExecutor threads, and zmq
|
|
356
|
+
# sockets are not thread-safe. eventfd_write() is atomic.
|
|
357
|
+
self._output_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
358
|
+
self.output_queue: queue.Queue = queue.Queue()
|
|
359
|
+
|
|
360
|
+
# Poller
|
|
361
|
+
self.poller = zmq.Poller()
|
|
362
|
+
self.poller.register(self.socket, zmq.POLLIN)
|
|
363
|
+
self.poller.register(self._output_efd, zmq.POLLIN)
|
|
364
|
+
|
|
365
|
+
# Main loop thread
|
|
366
|
+
self.is_finished = threading.Event()
|
|
367
|
+
self.worker_thread = threading.Thread(
|
|
368
|
+
target=self._main_loop, daemon=True, name="mq-server-thread"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# Registered handlers: request_type -> (payload_cls, handler)
|
|
372
|
+
self.handlers: dict[RequestType, RequestHandlerBase[Any]] = {}
|
|
373
|
+
|
|
374
|
+
# Thread pools assigned via add_normal_thread_pool / add_affinity_thread_pool
|
|
375
|
+
self.extra_pools: list[ThreadPoolExecutor | AffinityThreadPool] = []
|
|
376
|
+
|
|
377
|
+
def _call_sync_handler(
|
|
378
|
+
self,
|
|
379
|
+
handler_entry: SyncRequestHandler[Any],
|
|
380
|
+
payloads: list[bytes],
|
|
381
|
+
prefix_frames: list[bytes],
|
|
382
|
+
) -> Any:
|
|
383
|
+
"""
|
|
384
|
+
Call the sync handler and send the response back to the client.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
handler_entry (SyncRequestHandler[Any]): The handler entry.
|
|
388
|
+
payloads (list[bytes]): The payloads of the request.
|
|
389
|
+
prefix_frames (list[bytes]): The prefix frames to send back.
|
|
390
|
+
"""
|
|
391
|
+
response = handler_entry(payloads)
|
|
392
|
+
response_cls = handler_entry.get_response_class()
|
|
393
|
+
b_response = msgspec_encode(response, cls=response_cls)
|
|
394
|
+
if response is not None:
|
|
395
|
+
self.socket.send_multipart(prefix_frames + [b_response])
|
|
396
|
+
else:
|
|
397
|
+
self.socket.send_multipart(prefix_frames)
|
|
398
|
+
|
|
399
|
+
def _call_blocking_handler(
|
|
400
|
+
self,
|
|
401
|
+
handler_entry: BlockingRequestHandler[Any],
|
|
402
|
+
payloads: list[bytes],
|
|
403
|
+
prefix_frames: list[bytes],
|
|
404
|
+
) -> Any:
|
|
405
|
+
"""
|
|
406
|
+
Call the blocking handler in a separate thread and send the response
|
|
407
|
+
back to the client.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
handler_entry (BlockingRequestHandler[Any]): The handler entry.
|
|
411
|
+
payloads (list[bytes]): The payloads of the request.
|
|
412
|
+
prefix_frames (list[bytes]): The prefix frames to send back.
|
|
413
|
+
prefix_frames[0] is the zmq identity used as affinity key.
|
|
414
|
+
"""
|
|
415
|
+
affinity_key = hash(prefix_frames[0])
|
|
416
|
+
future = handler_entry(payloads, affinity_key=affinity_key)
|
|
417
|
+
|
|
418
|
+
def _notify_response(fut: Future):
|
|
419
|
+
try:
|
|
420
|
+
response = fut.result()
|
|
421
|
+
response_cls = handler_entry.get_response_class()
|
|
422
|
+
b_response = msgspec_encode(response, cls=response_cls)
|
|
423
|
+
frames_to_send = (
|
|
424
|
+
prefix_frames + [b_response]
|
|
425
|
+
if response is not None
|
|
426
|
+
else prefix_frames
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self.output_queue.put(frames_to_send)
|
|
430
|
+
os.eventfd_write(self._output_efd, 1)
|
|
431
|
+
|
|
432
|
+
except Exception:
|
|
433
|
+
logger.exception("Error in blocking handler")
|
|
434
|
+
|
|
435
|
+
future.add_done_callback(_notify_response)
|
|
436
|
+
|
|
437
|
+
def _call_handler(
|
|
438
|
+
self,
|
|
439
|
+
handler_entry: RequestHandlerBase[Any],
|
|
440
|
+
payloads: list[bytes],
|
|
441
|
+
prefix_frames: list[bytes],
|
|
442
|
+
) -> Any:
|
|
443
|
+
match handler_entry.get_handler_type():
|
|
444
|
+
case HandlerType.SYNC:
|
|
445
|
+
assert isinstance(handler_entry, SyncRequestHandler)
|
|
446
|
+
self._call_sync_handler(handler_entry, payloads, prefix_frames)
|
|
447
|
+
case HandlerType.BLOCKING:
|
|
448
|
+
assert isinstance(handler_entry, BlockingRequestHandler)
|
|
449
|
+
self._call_blocking_handler(handler_entry, payloads, prefix_frames)
|
|
450
|
+
case HandlerType.NON_BLOCKING:
|
|
451
|
+
raise NotImplementedError("Non-blocking handler is not supported yet")
|
|
452
|
+
case _:
|
|
453
|
+
raise ValueError("Unknown handler type")
|
|
454
|
+
|
|
455
|
+
def _main_loop(self):
|
|
456
|
+
while not self.is_finished.is_set():
|
|
457
|
+
socks = dict(self.poller.poll(1000))
|
|
458
|
+
inbound_state = socks.get(self.socket, None)
|
|
459
|
+
outbound_state = socks.get(self._output_efd, None)
|
|
460
|
+
|
|
461
|
+
# Process the incoming requests
|
|
462
|
+
if inbound_state and inbound_state & zmq.POLLIN:
|
|
463
|
+
msg = self.socket.recv_multipart()
|
|
464
|
+
assert len(msg) >= 3, (
|
|
465
|
+
"Expected at least 3 message parts "
|
|
466
|
+
"[identity, request_uid, request_type, *payloads]"
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
identity, b_request_uid, b_request_type, *payloads = msg
|
|
470
|
+
request_type = msgspec_decode(b_request_type, cls=RequestType)
|
|
471
|
+
|
|
472
|
+
if handler_entry := self.handlers.get(request_type):
|
|
473
|
+
try:
|
|
474
|
+
self._call_handler(
|
|
475
|
+
handler_entry=handler_entry,
|
|
476
|
+
payloads=payloads,
|
|
477
|
+
prefix_frames=[identity, b_request_uid, b_request_type],
|
|
478
|
+
)
|
|
479
|
+
except Exception:
|
|
480
|
+
logger.exception("Error handling request %s", request_type)
|
|
481
|
+
else:
|
|
482
|
+
logger.error(
|
|
483
|
+
"No handler registered for request type %s", request_type
|
|
484
|
+
)
|
|
485
|
+
logger.error("Available handlers: %s", list(self.handlers.keys()))
|
|
486
|
+
|
|
487
|
+
# Send the responses
|
|
488
|
+
if outbound_state and outbound_state & zmq.POLLIN:
|
|
489
|
+
# Consume the eventfd counter (resets atomically)
|
|
490
|
+
os.eventfd_read(self._output_efd)
|
|
491
|
+
|
|
492
|
+
# Process the output tasks
|
|
493
|
+
try:
|
|
494
|
+
while frames_to_send := self.output_queue.get_nowait():
|
|
495
|
+
self.socket.send_multipart(frames_to_send)
|
|
496
|
+
except queue.Empty:
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
def _inspect_handler_signature(self, request_type: RequestType, handler) -> bool:
|
|
500
|
+
"""Inspect the handler signature to ensure it matches the expected
|
|
501
|
+
payload classes.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
handler (callable): The handler function.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
bool: True if the signature matches, False otherwise.
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
def same_type(a, b) -> bool:
|
|
511
|
+
if a is None:
|
|
512
|
+
a = type(None)
|
|
513
|
+
if b is None:
|
|
514
|
+
b = type(None)
|
|
515
|
+
return a == b
|
|
516
|
+
|
|
517
|
+
sig = inspect.signature(handler)
|
|
518
|
+
hints = get_type_hints(handler)
|
|
519
|
+
params = [
|
|
520
|
+
p
|
|
521
|
+
for p in sig.parameters.values()
|
|
522
|
+
if p.kind
|
|
523
|
+
in (
|
|
524
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
525
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
526
|
+
)
|
|
527
|
+
]
|
|
528
|
+
|
|
529
|
+
payload_clss = get_payload_classes(request_type)
|
|
530
|
+
if len(params) != len(payload_clss):
|
|
531
|
+
logger.error(
|
|
532
|
+
"Handler for %s expects %d arguments, but got %d",
|
|
533
|
+
request_type,
|
|
534
|
+
len(payload_clss),
|
|
535
|
+
len(params),
|
|
536
|
+
)
|
|
537
|
+
return False
|
|
538
|
+
|
|
539
|
+
for i, (param, expected_cls) in enumerate(
|
|
540
|
+
zip(params, payload_clss, strict=False)
|
|
541
|
+
):
|
|
542
|
+
ann = hints.get(param.name, param.annotation)
|
|
543
|
+
if not same_type(ann, expected_cls):
|
|
544
|
+
logger.error(
|
|
545
|
+
"Handler for %s argument %d expects type %s, but got %s",
|
|
546
|
+
request_type,
|
|
547
|
+
i,
|
|
548
|
+
expected_cls,
|
|
549
|
+
ann,
|
|
550
|
+
)
|
|
551
|
+
return False
|
|
552
|
+
|
|
553
|
+
return_ann = hints.get("return", sig.return_annotation)
|
|
554
|
+
expected_return_cls = get_response_class(request_type)
|
|
555
|
+
if not same_type(return_ann, expected_return_cls):
|
|
556
|
+
logger.error(
|
|
557
|
+
"Handler for %s expects return type %s, but got %s",
|
|
558
|
+
request_type,
|
|
559
|
+
expected_return_cls,
|
|
560
|
+
return_ann,
|
|
561
|
+
)
|
|
562
|
+
return False
|
|
563
|
+
return True
|
|
564
|
+
|
|
565
|
+
def add_handler(
|
|
566
|
+
self,
|
|
567
|
+
request_type: RequestType,
|
|
568
|
+
payload_clss: list[Any],
|
|
569
|
+
handler_type: HandlerType,
|
|
570
|
+
handler,
|
|
571
|
+
) -> None:
|
|
572
|
+
"""Register a handler for a specific request type.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
request_type (RequestType): The type of the request to handle.
|
|
576
|
+
payload_clss (list[Any]): The expected payload classes for the request.
|
|
577
|
+
This should be get from `get_payload_classes(request_type)`.
|
|
578
|
+
handler (callable): The handler function that takes the payloads
|
|
579
|
+
as arguments.
|
|
580
|
+
"""
|
|
581
|
+
if not self._inspect_handler_signature(request_type, handler):
|
|
582
|
+
raise ValueError(
|
|
583
|
+
f"Handler signature does not match for request type: {request_type}"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
match handler_type:
|
|
587
|
+
case HandlerType.SYNC:
|
|
588
|
+
self.add_sync_handler(request_type, payload_clss, handler)
|
|
589
|
+
case HandlerType.BLOCKING:
|
|
590
|
+
self.add_blocking_handler(request_type, payload_clss, handler)
|
|
591
|
+
case HandlerType.NON_BLOCKING:
|
|
592
|
+
raise NotImplementedError("Non-blocking handler is not supported yet")
|
|
593
|
+
case _:
|
|
594
|
+
raise ValueError(f"Unknown handler type: {handler_type}")
|
|
595
|
+
|
|
596
|
+
def add_sync_handler(
|
|
597
|
+
self, request_type: RequestType, payload_clss: list[Any], handler
|
|
598
|
+
) -> None:
|
|
599
|
+
response_cls = get_response_class(request_type)
|
|
600
|
+
self.handlers[request_type] = SyncRequestHandler(
|
|
601
|
+
payload_clss, response_cls, handler
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
def add_blocking_handler(
|
|
605
|
+
self, request_type: RequestType, payload_clss: list[Any], handler
|
|
606
|
+
) -> None:
|
|
607
|
+
response_cls = get_response_class(request_type)
|
|
608
|
+
self.handlers[request_type] = BlockingRequestHandler(
|
|
609
|
+
payload_clss, response_cls, handler
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
def add_nonblocking_handler(
|
|
613
|
+
self, request_type: RequestType, payload_clss: list[Any], handler
|
|
614
|
+
) -> None:
|
|
615
|
+
raise NotImplementedError
|
|
616
|
+
|
|
617
|
+
def _validate_blocking_handlers(
|
|
618
|
+
self,
|
|
619
|
+
request_types: list[RequestType],
|
|
620
|
+
method_name: str,
|
|
621
|
+
) -> None:
|
|
622
|
+
"""Validate that all request types are registered BlockingRequestHandlers."""
|
|
623
|
+
for request_type in request_types:
|
|
624
|
+
handler = self.handlers.get(request_type)
|
|
625
|
+
if handler is None:
|
|
626
|
+
raise ValueError(
|
|
627
|
+
f"No handler registered for request type: {request_type}. "
|
|
628
|
+
f"Register handlers before calling {method_name}."
|
|
629
|
+
)
|
|
630
|
+
if not isinstance(handler, BlockingRequestHandler):
|
|
631
|
+
raise TypeError(
|
|
632
|
+
f"Handler for {request_type} is "
|
|
633
|
+
f"{type(handler).__name__}, not BlockingRequestHandler. "
|
|
634
|
+
f"Only blocking handlers can use thread pools."
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
def add_normal_thread_pool(
|
|
638
|
+
self,
|
|
639
|
+
request_types: list[RequestType],
|
|
640
|
+
max_workers: int,
|
|
641
|
+
) -> None:
|
|
642
|
+
"""Assign a ThreadPoolExecutor to specific request types.
|
|
643
|
+
|
|
644
|
+
Use this for non-GPU blocking handlers (e.g. LOOKUP, END_SESSION).
|
|
645
|
+
|
|
646
|
+
Must be called after the handlers are registered (via add_handler /
|
|
647
|
+
add_blocking_handler) and before start(). Each request_type must
|
|
648
|
+
already be registered as a BlockingRequestHandler; otherwise a
|
|
649
|
+
ValueError or TypeError is raised.
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
request_types: The request types that should use this pool.
|
|
653
|
+
max_workers: Number of worker threads in the pool.
|
|
654
|
+
"""
|
|
655
|
+
self._validate_blocking_handlers(request_types, "add_normal_thread_pool")
|
|
656
|
+
if not request_types:
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
pool = ThreadPoolExecutor(
|
|
660
|
+
max_workers=max_workers,
|
|
661
|
+
thread_name_prefix=f"normal-pool-{len(self.extra_pools)}",
|
|
662
|
+
)
|
|
663
|
+
self.extra_pools.append(pool)
|
|
664
|
+
for request_type in request_types:
|
|
665
|
+
handler = self.handlers[request_type]
|
|
666
|
+
assert isinstance(handler, BlockingRequestHandler)
|
|
667
|
+
handler.executor = pool
|
|
668
|
+
|
|
669
|
+
logger.debug(
|
|
670
|
+
"Created normal thread pool (max_workers=%d) for request types: %s",
|
|
671
|
+
max_workers,
|
|
672
|
+
[rt.name for rt in request_types],
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
def add_affinity_thread_pool(
|
|
676
|
+
self,
|
|
677
|
+
request_types: list[RequestType],
|
|
678
|
+
max_workers: int,
|
|
679
|
+
) -> None:
|
|
680
|
+
"""Assign an AffinityThreadPool to specific request types.
|
|
681
|
+
|
|
682
|
+
Use this for GPU-bound blocking handlers (e.g. STORE, RETRIEVE).
|
|
683
|
+
Requests from the same zmq client identity are always dispatched
|
|
684
|
+
to the same worker thread, eliminating the need for per-instance
|
|
685
|
+
GPU transfer locks.
|
|
686
|
+
|
|
687
|
+
Must be called after the handlers are registered (via add_handler /
|
|
688
|
+
add_blocking_handler) and before start().
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
request_types: The request types that should use this pool.
|
|
692
|
+
max_workers: Number of worker threads in the pool.
|
|
693
|
+
"""
|
|
694
|
+
self._validate_blocking_handlers(request_types, "add_affinity_thread_pool")
|
|
695
|
+
if not request_types:
|
|
696
|
+
return
|
|
697
|
+
|
|
698
|
+
pool = AffinityThreadPool(
|
|
699
|
+
max_workers=max_workers,
|
|
700
|
+
thread_name_prefix=f"affinity-pool-{len(self.extra_pools)}",
|
|
701
|
+
)
|
|
702
|
+
self.extra_pools.append(pool)
|
|
703
|
+
for request_type in request_types:
|
|
704
|
+
handler = self.handlers[request_type]
|
|
705
|
+
assert isinstance(handler, BlockingRequestHandler)
|
|
706
|
+
handler.executor = pool
|
|
707
|
+
|
|
708
|
+
logger.debug(
|
|
709
|
+
"Created affinity thread pool (max_workers=%d) for request types: %s",
|
|
710
|
+
max_workers,
|
|
711
|
+
[rt.name for rt in request_types],
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
def start(self):
|
|
715
|
+
# Validate all blocking handlers have an executor assigned
|
|
716
|
+
for rt, handler in self.handlers.items():
|
|
717
|
+
if isinstance(handler, BlockingRequestHandler) and handler.executor is None:
|
|
718
|
+
raise RuntimeError(
|
|
719
|
+
f"BlockingRequestHandler for {rt} has no thread pool "
|
|
720
|
+
f"assigned. Call add_normal_thread_pool or "
|
|
721
|
+
f"add_affinity_thread_pool before start()."
|
|
722
|
+
)
|
|
723
|
+
self.worker_thread.start()
|
|
724
|
+
|
|
725
|
+
def close(self) -> None:
|
|
726
|
+
self.is_finished.set()
|
|
727
|
+
if self.worker_thread.is_alive():
|
|
728
|
+
self.worker_thread.join()
|
|
729
|
+
self.socket.close()
|
|
730
|
+
for pool in self.extra_pools:
|
|
731
|
+
pool.shutdown(wait=False)
|
|
732
|
+
os.close(self._output_efd)
|