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,987 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import IntEnum, auto
|
|
5
|
+
from multiprocessing import shared_memory
|
|
6
|
+
from typing import AsyncIterator, List, Optional, Tuple
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
import urllib.parse
|
|
11
|
+
|
|
12
|
+
# Third Party
|
|
13
|
+
import aiohttp
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
# First Party
|
|
17
|
+
from lmcache.logging import init_logger
|
|
18
|
+
from lmcache.observability import LMCStatsMonitor
|
|
19
|
+
from lmcache.utils import CacheEngineKey
|
|
20
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
21
|
+
from lmcache.v1.protocol import RemoteMetadata
|
|
22
|
+
from lmcache.v1.storage_backend.connector.base_connector import RemoteConnector
|
|
23
|
+
from lmcache.v1.storage_backend.job_executor.pq_executor import AsyncPQExecutor
|
|
24
|
+
from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
|
|
25
|
+
|
|
26
|
+
logger = init_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Constants
|
|
30
|
+
METADATA_SIZE_BYTES = 28 # RemoteMetadata is 7 int32 fields
|
|
31
|
+
METADATA_SHAPE_DIMS = 4 # Number of shape dimensions in metadata
|
|
32
|
+
DEFAULT_CHUNK_SIZE_BYTES = 65536 # 64KB default for streaming
|
|
33
|
+
HTTP_OK = 200
|
|
34
|
+
HTTP_NO_CONTENT = 204
|
|
35
|
+
HTTP_NOT_FOUND = 404
|
|
36
|
+
HTTP_CONFLICT = 409
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Priorities(IntEnum):
|
|
40
|
+
"""Priority levels for job execution in the priority queue."""
|
|
41
|
+
|
|
42
|
+
LEASE = 0 # Highest priority - lease acquisition/release
|
|
43
|
+
PREFETCH = auto() # Medium priority - prefetching data
|
|
44
|
+
PUT = auto() # Lower priority - storing data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class LeaseInfo:
|
|
49
|
+
"""Information about a lease obtained from ai-toolkit daemon.
|
|
50
|
+
|
|
51
|
+
A lease represents temporary exclusive access to cached data in shared memory.
|
|
52
|
+
The daemon manages leases to prevent data from being evicted while in use.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
lease_id: str
|
|
56
|
+
offsets: List[Tuple[int, int]] # (offset, length) pairs in shared memory
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SageMakerHyperPodConnector(RemoteConnector):
|
|
60
|
+
"""
|
|
61
|
+
SageMaker HyperPod remote connector for communicating with KV cache daemon
|
|
62
|
+
in SageMaker HyperPod.
|
|
63
|
+
|
|
64
|
+
This connector provides high-performance access to KV cache data stored in
|
|
65
|
+
a remote SageMaker HyperPod service using:
|
|
66
|
+
- Shared memory (data plane) - zero-copy access via shared memory segment
|
|
67
|
+
- HTTP (control plane) - lease acquisition, release, and PUT operations
|
|
68
|
+
|
|
69
|
+
The connector uses a lease-based protocol with immediate release after all reads
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
sagemaker_hyperpod_url: str,
|
|
75
|
+
loop: asyncio.AbstractEventLoop,
|
|
76
|
+
local_cpu_backend: LocalCPUBackend,
|
|
77
|
+
bucket_name: str,
|
|
78
|
+
shared_memory_name: Optional[str],
|
|
79
|
+
max_concurrent_requests: int,
|
|
80
|
+
max_connections: int,
|
|
81
|
+
max_connections_per_host: int,
|
|
82
|
+
timeout_ms: int,
|
|
83
|
+
lease_ttl_s: float = 10.0,
|
|
84
|
+
put_stream_chunk_bytes: int = DEFAULT_CHUNK_SIZE_BYTES,
|
|
85
|
+
max_lease_size_mb: Optional[float] = None,
|
|
86
|
+
**kwargs, # Accept and ignore unused legacy parameters
|
|
87
|
+
):
|
|
88
|
+
"""
|
|
89
|
+
Initialize SageMaker HyperPod connector.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
sagemaker_hyperpod_url: Base URL of the ai-toolkit daemon
|
|
93
|
+
loop: Event loop for async operations
|
|
94
|
+
local_cpu_backend: Backend for local memory allocation
|
|
95
|
+
bucket_name: Bucket name for KV storage namespace
|
|
96
|
+
shared_memory_name: Name of shared memory segment
|
|
97
|
+
(if None, shared memory disabled)
|
|
98
|
+
max_concurrent_requests: Maximum concurrent control plane requests
|
|
99
|
+
max_connections: Maximum total HTTP connections in pool
|
|
100
|
+
max_connections_per_host: Maximum HTTP connections per host
|
|
101
|
+
timeout_ms: Timeout for lease acquisition requests
|
|
102
|
+
lease_ttl_s: Server-side lease timeout (default: 10s)
|
|
103
|
+
put_stream_chunk_bytes: Chunk size for
|
|
104
|
+
streaming PUT requests (default: 64KB)
|
|
105
|
+
**kwargs: Unused legacy parameters (ignored for backward compatibility)
|
|
106
|
+
"""
|
|
107
|
+
# initialize base class, which includes some common attributes
|
|
108
|
+
super().__init__(local_cpu_backend.config, local_cpu_backend.metadata)
|
|
109
|
+
|
|
110
|
+
# Core configuration
|
|
111
|
+
self.base_url = sagemaker_hyperpod_url.rstrip("/")
|
|
112
|
+
self.loop = loop
|
|
113
|
+
self.local_cpu_backend = local_cpu_backend
|
|
114
|
+
self.bucket_name = bucket_name
|
|
115
|
+
self.shared_memory_name = shared_memory_name
|
|
116
|
+
self.lease_ttl_s = lease_ttl_s
|
|
117
|
+
self.put_stream_chunk_bytes = max(1024, put_stream_chunk_bytes) # Minimum 1KB
|
|
118
|
+
self.max_lease_size_bytes = (
|
|
119
|
+
int(max_lease_size_mb * 1024 * 1024) if max_lease_size_mb else None
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# HTTP configuration
|
|
123
|
+
self.max_concurrent_requests = max(1, max_concurrent_requests)
|
|
124
|
+
self.max_connections = max(1, max_connections)
|
|
125
|
+
self.max_connections_per_host = max(1, max_connections_per_host)
|
|
126
|
+
self.timeout_ms = max(100, timeout_ms) # Minimum 100ms
|
|
127
|
+
|
|
128
|
+
# HTTP session (lazy initialized)
|
|
129
|
+
self.http_session: Optional[aiohttp.ClientSession] = None
|
|
130
|
+
self.session_lock = asyncio.Lock()
|
|
131
|
+
|
|
132
|
+
# Concurrency control
|
|
133
|
+
self.control_inflight = asyncio.Semaphore(self.max_concurrent_requests)
|
|
134
|
+
self.put_inflight = asyncio.Semaphore(self.max_concurrent_requests)
|
|
135
|
+
self.pq_executor = AsyncPQExecutor(loop)
|
|
136
|
+
|
|
137
|
+
# Shared memory
|
|
138
|
+
self.shared_memory_obj: Optional[shared_memory.SharedMemory] = None
|
|
139
|
+
self.shared_memory_map: Optional[memoryview] = None
|
|
140
|
+
if self.shared_memory_name:
|
|
141
|
+
self._init_shared_memory()
|
|
142
|
+
|
|
143
|
+
# Observability
|
|
144
|
+
self._stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
145
|
+
|
|
146
|
+
# Statistics for monitoring
|
|
147
|
+
self.stats = {
|
|
148
|
+
"get_success": 0,
|
|
149
|
+
"get_failure": 0,
|
|
150
|
+
"put_success": 0,
|
|
151
|
+
"put_failure": 0,
|
|
152
|
+
"lease_acquired": 0,
|
|
153
|
+
"lease_released": 0,
|
|
154
|
+
"lease_release_failed": 0,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logger.info(
|
|
158
|
+
f"SageMaker HyperPod Connector initialized: url={self.base_url}, "
|
|
159
|
+
f"bucket={self.bucket_name}, shared_memory={self.shared_memory_name}, "
|
|
160
|
+
f"connections={self.max_connections}, lease_ttl={lease_ttl_s}s"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _init_shared_memory(self):
|
|
164
|
+
"""Initialize shared memory connection to ai-toolkit daemon."""
|
|
165
|
+
try:
|
|
166
|
+
self.shared_memory_obj = shared_memory.SharedMemory(
|
|
167
|
+
name=self.shared_memory_name, create=False
|
|
168
|
+
)
|
|
169
|
+
self.shared_memory_map = memoryview(self.shared_memory_obj.buf)
|
|
170
|
+
size_mb = len(self.shared_memory_map) / (1024**2)
|
|
171
|
+
logger.info(
|
|
172
|
+
f"Shared memory opened: {self.shared_memory_name} ({size_mb:.2f} MB)"
|
|
173
|
+
)
|
|
174
|
+
except FileNotFoundError:
|
|
175
|
+
logger.error(
|
|
176
|
+
f"Shared memory segment '{self.shared_memory_name}' not found. "
|
|
177
|
+
"Ensure ai-toolkit daemon is running."
|
|
178
|
+
)
|
|
179
|
+
self.shared_memory_map = None
|
|
180
|
+
raise
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Failed to initialize shared memory: {e}")
|
|
183
|
+
self.shared_memory_map = None
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
async def _ensure_http_session(self) -> aiohttp.ClientSession:
|
|
187
|
+
"""Ensure HTTP session with connection pooling is initialized."""
|
|
188
|
+
if self.http_session is None:
|
|
189
|
+
async with self.session_lock:
|
|
190
|
+
if self.http_session is None: # Double-check locking
|
|
191
|
+
connector = aiohttp.TCPConnector(
|
|
192
|
+
limit=self.max_connections,
|
|
193
|
+
limit_per_host=self.max_connections_per_host,
|
|
194
|
+
ttl_dns_cache=300,
|
|
195
|
+
use_dns_cache=True,
|
|
196
|
+
keepalive_timeout=30,
|
|
197
|
+
enable_cleanup_closed=True,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
timeout = aiohttp.ClientTimeout(
|
|
201
|
+
total=30,
|
|
202
|
+
connect=5,
|
|
203
|
+
sock_read=10,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
self.http_session = aiohttp.ClientSession(
|
|
207
|
+
connector=connector,
|
|
208
|
+
timeout=timeout,
|
|
209
|
+
headers={"User-Agent": "LMCache-SageMaker-HyperPod/1.0"},
|
|
210
|
+
)
|
|
211
|
+
logger.info(
|
|
212
|
+
f"HTTP session created with {self.max_connections} "
|
|
213
|
+
f"max connections"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return self.http_session
|
|
217
|
+
|
|
218
|
+
async def _http_request(
|
|
219
|
+
self,
|
|
220
|
+
method: str,
|
|
221
|
+
url: str,
|
|
222
|
+
data=None,
|
|
223
|
+
params=None,
|
|
224
|
+
timeout: float = 5.0,
|
|
225
|
+
headers=None,
|
|
226
|
+
gate: Optional[asyncio.Semaphore] = None,
|
|
227
|
+
):
|
|
228
|
+
"""Execute HTTP request with optional semaphore gate."""
|
|
229
|
+
if gate is None:
|
|
230
|
+
return await self._http_request_impl(
|
|
231
|
+
method, url, data=data, params=params, timeout=timeout, headers=headers
|
|
232
|
+
)
|
|
233
|
+
async with gate:
|
|
234
|
+
return await self._http_request_impl(
|
|
235
|
+
method, url, data=data, params=params, timeout=timeout, headers=headers
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
async def _http_request_impl(
|
|
239
|
+
self,
|
|
240
|
+
method: str,
|
|
241
|
+
url: str,
|
|
242
|
+
data=None,
|
|
243
|
+
params=None,
|
|
244
|
+
timeout: float = 5.0,
|
|
245
|
+
headers=None,
|
|
246
|
+
):
|
|
247
|
+
"""Execute HTTP request with connection pooling and error handling."""
|
|
248
|
+
try:
|
|
249
|
+
session = await self._ensure_http_session()
|
|
250
|
+
request_timeout = aiohttp.ClientTimeout(total=timeout)
|
|
251
|
+
|
|
252
|
+
async with session.request(
|
|
253
|
+
method,
|
|
254
|
+
url,
|
|
255
|
+
data=data,
|
|
256
|
+
params=params,
|
|
257
|
+
timeout=request_timeout,
|
|
258
|
+
headers=headers,
|
|
259
|
+
) as response:
|
|
260
|
+
# Parse JSON response if available
|
|
261
|
+
body_json = None
|
|
262
|
+
content_type = response.headers.get("Content-Type", "")
|
|
263
|
+
if content_type.startswith("application/json"):
|
|
264
|
+
try:
|
|
265
|
+
body_json = await response.json()
|
|
266
|
+
except aiohttp.ContentTypeError as e:
|
|
267
|
+
logger.warning(
|
|
268
|
+
f"JSON parsing failed for {method} {url}:"
|
|
269
|
+
f"invalid content-type - {e}"
|
|
270
|
+
)
|
|
271
|
+
except json.JSONDecodeError as e:
|
|
272
|
+
logger.warning(
|
|
273
|
+
f"JSON parsing failed for {method} {url}:"
|
|
274
|
+
f"malformed JSON - {e}"
|
|
275
|
+
)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.warning(f"JSON parsing failed for {method} {url}: {e}")
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
"status": response.status,
|
|
281
|
+
"json": body_json,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
except asyncio.TimeoutError:
|
|
285
|
+
logger.warning(f"HTTP {method} timeout: {url}")
|
|
286
|
+
return None
|
|
287
|
+
except aiohttp.ClientError as e:
|
|
288
|
+
logger.error(f"HTTP {method} client error: {url} - {e}")
|
|
289
|
+
return None
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.error(f"HTTP {method} failed: {url} - {e}")
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def _key_to_string(self, key: CacheEngineKey) -> str:
|
|
295
|
+
"""Convert CacheEngineKey to URL-safe string format."""
|
|
296
|
+
key_str = key.to_string()
|
|
297
|
+
return urllib.parse.quote(key_str, safe="")
|
|
298
|
+
|
|
299
|
+
async def _release_lease(self, key: CacheEngineKey, lease_id: str) -> bool:
|
|
300
|
+
"""
|
|
301
|
+
Release a lease to free server resources immediately.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
key: The cache key
|
|
305
|
+
lease_id: The lease ID to release
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if release successful, False on error
|
|
309
|
+
"""
|
|
310
|
+
key_str = self._key_to_string(key)
|
|
311
|
+
url = f"{self.base_url}/v1/leases/{lease_id}/release"
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
result = await self._http_request(
|
|
315
|
+
"POST",
|
|
316
|
+
url,
|
|
317
|
+
timeout=5.0,
|
|
318
|
+
gate=self.control_inflight,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if result and result["status"] == HTTP_OK:
|
|
322
|
+
self.stats["lease_released"] += 1
|
|
323
|
+
logger.debug(f"Lease released: key={key_str}, lease_id={lease_id}")
|
|
324
|
+
return True
|
|
325
|
+
else:
|
|
326
|
+
status = result["status"] if result else "TIMEOUT"
|
|
327
|
+
self.stats["lease_release_failed"] += 1
|
|
328
|
+
logger.warning(
|
|
329
|
+
f"Lease release failed: key={key_str}, lease_id={lease_id}, "
|
|
330
|
+
f"status={status}"
|
|
331
|
+
)
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self.stats["lease_release_failed"] += 1
|
|
336
|
+
logger.warning(
|
|
337
|
+
f"Lease release error: key={key_str}, lease_id={lease_id} - {e}"
|
|
338
|
+
)
|
|
339
|
+
return False
|
|
340
|
+
|
|
341
|
+
async def _acquire_lease(self, key: CacheEngineKey) -> Optional[LeaseInfo]:
|
|
342
|
+
"""
|
|
343
|
+
Acquire a lease for the given key.
|
|
344
|
+
|
|
345
|
+
A lease prevents the daemon from evicting data while we're reading it.
|
|
346
|
+
The response includes offset information for shared memory access.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
key: The cache key to acquire lease for
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
LeaseInfo if successful, None otherwise
|
|
353
|
+
"""
|
|
354
|
+
key_str = self._key_to_string(key)
|
|
355
|
+
url = f"{self.base_url}/v1/kv/{self.bucket_name}/{key_str}/leases"
|
|
356
|
+
params = {
|
|
357
|
+
"timeout_ms": self.timeout_ms,
|
|
358
|
+
"ttl_s": self.lease_ttl_s,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
result = await self._http_request(
|
|
362
|
+
"POST",
|
|
363
|
+
url,
|
|
364
|
+
params=params,
|
|
365
|
+
timeout=self.timeout_ms / 1000.0,
|
|
366
|
+
gate=self.control_inflight,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if not result or result["status"] != HTTP_OK or not result["json"]:
|
|
370
|
+
logger.debug(f"Lease acquisition failed: key={key_str}")
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
lease_data = result["json"]
|
|
374
|
+
offsets = [(o["offset"], o["len"]) for o in lease_data.get("offsets", [])]
|
|
375
|
+
|
|
376
|
+
if not offsets:
|
|
377
|
+
logger.debug(f"Lease has no offsets: key={key_str}")
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
lease_info = LeaseInfo(
|
|
381
|
+
lease_id=lease_data["id"],
|
|
382
|
+
offsets=offsets,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
total_size = sum(length for _, length in offsets)
|
|
386
|
+
|
|
387
|
+
if (
|
|
388
|
+
self.max_lease_size_bytes is not None
|
|
389
|
+
and total_size > self.max_lease_size_bytes
|
|
390
|
+
):
|
|
391
|
+
logger.warning(
|
|
392
|
+
f"Lease size {total_size / 1024:.2f} KB exceeds limit "
|
|
393
|
+
f"{self.max_lease_size_bytes / 1024:.2f} KB, releasing"
|
|
394
|
+
)
|
|
395
|
+
await self._release_lease(key, lease_info.lease_id)
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
self.stats["lease_acquired"] += 1
|
|
399
|
+
|
|
400
|
+
logger.debug(
|
|
401
|
+
f"Lease acquired: key={key_str}, lease_id={lease_info.lease_id}, "
|
|
402
|
+
f"size={total_size / 1024:.2f} KB, blocks={len(offsets)}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
return lease_info
|
|
406
|
+
|
|
407
|
+
async def _executor_submit_lease_acquisition(
|
|
408
|
+
self, key: CacheEngineKey
|
|
409
|
+
) -> Optional[LeaseInfo]:
|
|
410
|
+
"""Submit lease acquisition to priority executor."""
|
|
411
|
+
return await self.pq_executor.submit_job(
|
|
412
|
+
self._acquire_lease,
|
|
413
|
+
key=key,
|
|
414
|
+
priority=Priorities.LEASE,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def _read_from_shared_memory(
|
|
418
|
+
self, key: CacheEngineKey, lease_info: LeaseInfo
|
|
419
|
+
) -> Optional[MemoryObj]:
|
|
420
|
+
"""
|
|
421
|
+
Read data from shared memory using lease offsets.
|
|
422
|
+
|
|
423
|
+
Data format: [RemoteMetadata header (28 bytes)] + [KV cache payload]
|
|
424
|
+
Data may be fragmented across multiple blocks in shared memory.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
key: The cache key being read
|
|
428
|
+
lease_info: Lease information with memory offsets
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
MemoryObj containing the data, or None on error
|
|
432
|
+
"""
|
|
433
|
+
if self.shared_memory_map is None:
|
|
434
|
+
logger.error("Shared memory not available")
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
if not lease_info.offsets:
|
|
438
|
+
logger.error("No offsets in lease")
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
shm_size = len(self.shared_memory_map)
|
|
442
|
+
for offset, length in lease_info.offsets:
|
|
443
|
+
if offset < 0 or length < 0:
|
|
444
|
+
logger.error(
|
|
445
|
+
f"Invalid offset or length: offset={offset}, length={length}"
|
|
446
|
+
)
|
|
447
|
+
return None
|
|
448
|
+
if offset + length > shm_size:
|
|
449
|
+
logger.error(
|
|
450
|
+
f"Offset out of bounds: offset={offset}, length={length}, "
|
|
451
|
+
f"shm_size={shm_size}"
|
|
452
|
+
)
|
|
453
|
+
return None
|
|
454
|
+
|
|
455
|
+
memory_obj = None
|
|
456
|
+
try:
|
|
457
|
+
# Validate total size
|
|
458
|
+
total_size = sum(length for _, length in lease_info.offsets)
|
|
459
|
+
if total_size < METADATA_SIZE_BYTES:
|
|
460
|
+
logger.error(f"Insufficient data for metadata: {total_size} bytes")
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
# Read metadata header (may span multiple blocks)
|
|
464
|
+
header = self._read_bytes_from_offsets(
|
|
465
|
+
lease_info.offsets, 0, METADATA_SIZE_BYTES
|
|
466
|
+
)
|
|
467
|
+
if len(header) < METADATA_SIZE_BYTES:
|
|
468
|
+
logger.error("Failed to read complete metadata header")
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
# Parse metadata
|
|
472
|
+
metadata = RemoteMetadata.deserialize(header)
|
|
473
|
+
if metadata.length <= 0:
|
|
474
|
+
logger.error(f"Invalid payload length: {metadata.length}")
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
# Restore original shape (remove padding zeros)
|
|
478
|
+
actual_shape = self._parse_shape(metadata.shapes[0])
|
|
479
|
+
|
|
480
|
+
# Allocate local CPU memory
|
|
481
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
482
|
+
actual_shape,
|
|
483
|
+
metadata.dtypes[0],
|
|
484
|
+
metadata.fmt,
|
|
485
|
+
)
|
|
486
|
+
if memory_obj is None:
|
|
487
|
+
logger.error(f"Failed to allocate memory for key {key.to_string()}")
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
# Get writable view
|
|
491
|
+
view = self._get_writable_view(memory_obj.byte_array)
|
|
492
|
+
|
|
493
|
+
# Copy payload data from shared memory (skip header)
|
|
494
|
+
copied = self._copy_bytes_from_offsets(
|
|
495
|
+
lease_info.offsets, METADATA_SIZE_BYTES, metadata.length, view
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
if copied != metadata.length:
|
|
499
|
+
logger.error(
|
|
500
|
+
f"Data size mismatch: expected {metadata.length}, got {copied}"
|
|
501
|
+
)
|
|
502
|
+
memory_obj.ref_count_down()
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
logger.debug(
|
|
506
|
+
"Read from shared memory: key=%s, shape=%s, dtype=%s, size=%s bytes",
|
|
507
|
+
key.to_string(),
|
|
508
|
+
actual_shape,
|
|
509
|
+
metadata.dtypes[0],
|
|
510
|
+
metadata.length,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
return memory_obj
|
|
514
|
+
|
|
515
|
+
except Exception as e:
|
|
516
|
+
logger.error(
|
|
517
|
+
f"Error reading from shared memory: key={key.to_string()} - {e}"
|
|
518
|
+
)
|
|
519
|
+
if memory_obj is not None:
|
|
520
|
+
memory_obj.ref_count_down()
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
def _read_bytes_from_offsets(
|
|
524
|
+
self, offsets: List[Tuple[int, int]], skip_bytes: int, read_bytes: int
|
|
525
|
+
) -> bytearray:
|
|
526
|
+
"""Read bytes from shared memory offsets, skipping initial bytes."""
|
|
527
|
+
if self.shared_memory_map is None:
|
|
528
|
+
logger.error("Shared memory not available")
|
|
529
|
+
return bytearray()
|
|
530
|
+
|
|
531
|
+
result = bytearray(read_bytes)
|
|
532
|
+
filled = 0
|
|
533
|
+
bytes_to_skip = skip_bytes
|
|
534
|
+
shm_size = len(self.shared_memory_map)
|
|
535
|
+
|
|
536
|
+
for offset, length in offsets:
|
|
537
|
+
if filled >= read_bytes:
|
|
538
|
+
break
|
|
539
|
+
|
|
540
|
+
# Skip header bytes in first chunk(s)
|
|
541
|
+
if bytes_to_skip > 0:
|
|
542
|
+
if length <= bytes_to_skip:
|
|
543
|
+
bytes_to_skip -= length
|
|
544
|
+
continue
|
|
545
|
+
offset += bytes_to_skip
|
|
546
|
+
length -= bytes_to_skip
|
|
547
|
+
bytes_to_skip = 0
|
|
548
|
+
|
|
549
|
+
if length <= 0:
|
|
550
|
+
continue
|
|
551
|
+
|
|
552
|
+
take = min(read_bytes - filled, length)
|
|
553
|
+
|
|
554
|
+
if offset < 0 or take <= 0:
|
|
555
|
+
logger.error(f"Invalid read parameters: offset={offset}, take={take}")
|
|
556
|
+
break
|
|
557
|
+
if offset + take > shm_size:
|
|
558
|
+
logger.error(
|
|
559
|
+
f"Read would exceed shared memory bounds: "
|
|
560
|
+
f"offset={offset}, take={take}, shm_size={shm_size}"
|
|
561
|
+
)
|
|
562
|
+
break
|
|
563
|
+
|
|
564
|
+
result[filled : filled + take] = self.shared_memory_map[
|
|
565
|
+
offset : offset + take
|
|
566
|
+
]
|
|
567
|
+
filled += take
|
|
568
|
+
|
|
569
|
+
return result
|
|
570
|
+
|
|
571
|
+
def _copy_bytes_from_offsets(
|
|
572
|
+
self,
|
|
573
|
+
offsets: List[Tuple[int, int]],
|
|
574
|
+
skip_bytes: int,
|
|
575
|
+
copy_bytes: int,
|
|
576
|
+
dest_view: memoryview,
|
|
577
|
+
) -> int:
|
|
578
|
+
"""Copy bytes from shared memory offsets to destination view."""
|
|
579
|
+
if self.shared_memory_map is None:
|
|
580
|
+
logger.error("Shared memory not available")
|
|
581
|
+
return 0
|
|
582
|
+
|
|
583
|
+
copied = 0
|
|
584
|
+
bytes_to_skip = skip_bytes
|
|
585
|
+
shm_size = len(self.shared_memory_map)
|
|
586
|
+
|
|
587
|
+
for offset, length in offsets:
|
|
588
|
+
if copied >= copy_bytes:
|
|
589
|
+
break
|
|
590
|
+
|
|
591
|
+
# Skip header bytes
|
|
592
|
+
if bytes_to_skip > 0:
|
|
593
|
+
if length <= bytes_to_skip:
|
|
594
|
+
bytes_to_skip -= length
|
|
595
|
+
continue
|
|
596
|
+
offset += bytes_to_skip
|
|
597
|
+
length -= bytes_to_skip
|
|
598
|
+
bytes_to_skip = 0
|
|
599
|
+
|
|
600
|
+
if length <= 0:
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
take = min(copy_bytes - copied, length)
|
|
604
|
+
|
|
605
|
+
if offset < 0 or take <= 0:
|
|
606
|
+
logger.error(f"Invalid copy parameters: offset={offset}, take={take}")
|
|
607
|
+
break
|
|
608
|
+
if offset + take > shm_size:
|
|
609
|
+
logger.error(
|
|
610
|
+
f"Copy would exceed shared memory bounds: "
|
|
611
|
+
f"offset={offset}, take={take}, shm_size={shm_size}"
|
|
612
|
+
)
|
|
613
|
+
break
|
|
614
|
+
|
|
615
|
+
dest_view[copied : copied + take] = self.shared_memory_map[
|
|
616
|
+
offset : offset + take
|
|
617
|
+
]
|
|
618
|
+
copied += take
|
|
619
|
+
|
|
620
|
+
return copied
|
|
621
|
+
|
|
622
|
+
@staticmethod
|
|
623
|
+
def _parse_shape(shape: torch.Size) -> torch.Size:
|
|
624
|
+
"""Parse shape from metadata, removing padding zeros."""
|
|
625
|
+
actual_shape_list: List[int] = []
|
|
626
|
+
for dim in shape:
|
|
627
|
+
if dim == 0 and len(actual_shape_list) > 0:
|
|
628
|
+
break
|
|
629
|
+
actual_shape_list.append(dim)
|
|
630
|
+
return torch.Size(actual_shape_list) if actual_shape_list else torch.Size([1])
|
|
631
|
+
|
|
632
|
+
@staticmethod
|
|
633
|
+
def _get_writable_view(byte_array) -> memoryview:
|
|
634
|
+
"""Get a writable memoryview from byte array."""
|
|
635
|
+
if isinstance(byte_array, memoryview):
|
|
636
|
+
view = byte_array
|
|
637
|
+
if getattr(view, "format", None) == "<B":
|
|
638
|
+
view = view.cast("B")
|
|
639
|
+
else:
|
|
640
|
+
view = memoryview(byte_array)
|
|
641
|
+
return view
|
|
642
|
+
|
|
643
|
+
async def exists(self, key: CacheEngineKey) -> bool:
|
|
644
|
+
"""
|
|
645
|
+
Check if a key exists in remote storage.
|
|
646
|
+
|
|
647
|
+
Acquires a lease, checks existence, then releases immediately.
|
|
648
|
+
"""
|
|
649
|
+
lease = await self._executor_submit_lease_acquisition(key)
|
|
650
|
+
if lease is None:
|
|
651
|
+
return False
|
|
652
|
+
|
|
653
|
+
try:
|
|
654
|
+
return True
|
|
655
|
+
finally:
|
|
656
|
+
await self._release_lease(key, lease.lease_id)
|
|
657
|
+
|
|
658
|
+
def exists_sync(self, key: CacheEngineKey) -> bool:
|
|
659
|
+
"""Check if a key exists in remote storage (sync wrapper)."""
|
|
660
|
+
future = asyncio.run_coroutine_threadsafe(self.exists(key), self.loop)
|
|
661
|
+
return bool(future.result())
|
|
662
|
+
|
|
663
|
+
async def get(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
664
|
+
"""
|
|
665
|
+
Retrieve KV cache data for the given key with metrics reporting.
|
|
666
|
+
|
|
667
|
+
Flow:
|
|
668
|
+
1. Acquire a new lease
|
|
669
|
+
2. Read from shared memory using lease offsets
|
|
670
|
+
3. Release lease immediately (in finally block)
|
|
671
|
+
4. Report metrics
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
key: The cache key to retrieve
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
MemoryObj containing the KV cache data, or None if not found
|
|
678
|
+
"""
|
|
679
|
+
begin = time.perf_counter()
|
|
680
|
+
|
|
681
|
+
lease_info = await self._executor_submit_lease_acquisition(key)
|
|
682
|
+
|
|
683
|
+
if lease_info is None:
|
|
684
|
+
self.stats["get_failure"] += 1
|
|
685
|
+
logger.debug(f"GET failed (no lease): key={key.to_string()}")
|
|
686
|
+
return None
|
|
687
|
+
|
|
688
|
+
try:
|
|
689
|
+
memory_obj = self._read_from_shared_memory(key, lease_info)
|
|
690
|
+
|
|
691
|
+
if memory_obj is not None:
|
|
692
|
+
end = time.perf_counter()
|
|
693
|
+
obj_size = memory_obj.get_size()
|
|
694
|
+
|
|
695
|
+
# Report metrics for successful get
|
|
696
|
+
self._stats_monitor.update_interval_remote_time_to_get(
|
|
697
|
+
(end - begin) * 1000
|
|
698
|
+
)
|
|
699
|
+
self._stats_monitor.update_interval_remote_read_metrics(obj_size)
|
|
700
|
+
|
|
701
|
+
self.stats["get_success"] += 1
|
|
702
|
+
logger.debug(
|
|
703
|
+
f"GET success: key={key.to_string()}, "
|
|
704
|
+
f"shape={memory_obj.get_shape()}, "
|
|
705
|
+
f"size={obj_size / 1e6:.3f} MBytes in {(end - begin) * 1000:.3f}ms"
|
|
706
|
+
)
|
|
707
|
+
else:
|
|
708
|
+
self.stats["get_failure"] += 1
|
|
709
|
+
logger.error(
|
|
710
|
+
f"Failed to read from shared memory: key={key.to_string()}"
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
return memory_obj
|
|
714
|
+
|
|
715
|
+
except Exception as e:
|
|
716
|
+
self.stats["get_failure"] += 1
|
|
717
|
+
logger.error(f"GET error: key={key.to_string()} - {e}")
|
|
718
|
+
return None
|
|
719
|
+
|
|
720
|
+
finally:
|
|
721
|
+
# Always release lease immediately after read
|
|
722
|
+
await self._release_lease(key, lease_info.lease_id)
|
|
723
|
+
|
|
724
|
+
async def batched_get(
|
|
725
|
+
self, keys: List[CacheEngineKey]
|
|
726
|
+
) -> List[Optional[MemoryObj]]:
|
|
727
|
+
"""Get multiple keys in parallel. Metrics reported per individual get."""
|
|
728
|
+
tasks = [self.get(key) for key in keys]
|
|
729
|
+
return await asyncio.gather(*tasks)
|
|
730
|
+
|
|
731
|
+
def support_batched_put(self) -> bool:
|
|
732
|
+
"""Indicate support for batched PUT operations."""
|
|
733
|
+
return True
|
|
734
|
+
|
|
735
|
+
async def batched_put(
|
|
736
|
+
self, keys: List[CacheEngineKey], memory_objs: List[MemoryObj]
|
|
737
|
+
):
|
|
738
|
+
"""Store multiple objects in parallel. Metrics reported per individual put."""
|
|
739
|
+
await asyncio.gather(
|
|
740
|
+
*(self.put(key, mem) for key, mem in zip(keys, memory_objs, strict=True))
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
async def put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
744
|
+
"""Store data to ai-toolkit (queued with priority)."""
|
|
745
|
+
return await self.pq_executor.submit_job(
|
|
746
|
+
self._put,
|
|
747
|
+
key=key,
|
|
748
|
+
memory_obj=memory_obj,
|
|
749
|
+
priority=Priorities.PUT,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
async def _put(self, key: CacheEngineKey, memory_obj: MemoryObj):
|
|
753
|
+
"""Internal PUT operation - sends data via HTTP streaming."""
|
|
754
|
+
key_str = self._key_to_string(key)
|
|
755
|
+
url = f"{self.base_url}/v1/kv/{self.bucket_name}/{key_str}"
|
|
756
|
+
|
|
757
|
+
begin = time.perf_counter()
|
|
758
|
+
|
|
759
|
+
try:
|
|
760
|
+
# Build streaming payload (header + data)
|
|
761
|
+
payload_len, payload_iter = self._build_put_stream(memory_obj)
|
|
762
|
+
|
|
763
|
+
logger.debug(
|
|
764
|
+
f"PUT: key={key_str}, size={payload_len / 1024:.2f} KB, "
|
|
765
|
+
f"shape={memory_obj.get_shape()}"
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
# Send HTTP PUT request with streaming
|
|
769
|
+
result = await self._http_request(
|
|
770
|
+
"PUT",
|
|
771
|
+
url,
|
|
772
|
+
data=payload_iter,
|
|
773
|
+
timeout=self.timeout_ms / 1000.0,
|
|
774
|
+
headers={"Content-Length": str(payload_len)},
|
|
775
|
+
gate=self.put_inflight,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
end = time.perf_counter()
|
|
779
|
+
|
|
780
|
+
if result and result["status"] == HTTP_OK:
|
|
781
|
+
self.stats["put_success"] += 1
|
|
782
|
+
|
|
783
|
+
# Report metrics for successful put
|
|
784
|
+
self._stats_monitor.update_interval_remote_time_to_put(
|
|
785
|
+
(end - begin) * 1000
|
|
786
|
+
)
|
|
787
|
+
self._stats_monitor.update_interval_remote_write_metrics(payload_len)
|
|
788
|
+
|
|
789
|
+
logger.info(
|
|
790
|
+
f"PUT success: key={key_str}, size={payload_len / 1024:.2f} KB "
|
|
791
|
+
f"in {(end - begin) * 1000:.3f}ms"
|
|
792
|
+
)
|
|
793
|
+
elif result and result["status"] == HTTP_CONFLICT:
|
|
794
|
+
# 409 Conflict = key already exists (not an error)
|
|
795
|
+
self.stats["put_success"] += 1
|
|
796
|
+
|
|
797
|
+
# Still report metrics for conflict case
|
|
798
|
+
self._stats_monitor.update_interval_remote_time_to_put(
|
|
799
|
+
(end - begin) * 1000
|
|
800
|
+
)
|
|
801
|
+
self._stats_monitor.update_interval_remote_write_metrics(payload_len)
|
|
802
|
+
|
|
803
|
+
logger.debug(f"PUT skipped (already exists): key={key_str}")
|
|
804
|
+
else:
|
|
805
|
+
status = result["status"] if result else "TIMEOUT"
|
|
806
|
+
self.stats["put_failure"] += 1
|
|
807
|
+
logger.error(f"PUT failed: key={key_str}, status={status}")
|
|
808
|
+
|
|
809
|
+
except Exception as e:
|
|
810
|
+
self.stats["put_failure"] += 1
|
|
811
|
+
logger.error(f"PUT exception: key={key_str} - {e}")
|
|
812
|
+
|
|
813
|
+
def _build_put_stream(self, memory_obj: MemoryObj) -> Tuple[int, AsyncIterator]:
|
|
814
|
+
"""
|
|
815
|
+
Build streaming payload: [RemoteMetadata (28 bytes)] + [KV cache data]
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
memory_obj: The memory object to stream
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
Tuple of (total_length, async_generator)
|
|
822
|
+
"""
|
|
823
|
+
# Prepare data view
|
|
824
|
+
kv_view = self._get_writable_view(memory_obj.byte_array)
|
|
825
|
+
kv_len = len(kv_view)
|
|
826
|
+
|
|
827
|
+
# Prepare metadata
|
|
828
|
+
shape = list(memory_obj.get_shape())
|
|
829
|
+
padded_shape = (shape + [0] * METADATA_SHAPE_DIMS)[:METADATA_SHAPE_DIMS]
|
|
830
|
+
|
|
831
|
+
metadata = RemoteMetadata(
|
|
832
|
+
kv_len,
|
|
833
|
+
[torch.Size(padded_shape)],
|
|
834
|
+
[memory_obj.get_dtype()],
|
|
835
|
+
memory_obj.get_memory_format(),
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
# Serialize metadata header
|
|
839
|
+
header = bytearray(METADATA_SIZE_BYTES)
|
|
840
|
+
metadata.serialize_into(header)
|
|
841
|
+
header_bytes = bytes(header)
|
|
842
|
+
|
|
843
|
+
total_len = len(header_bytes) + kv_len
|
|
844
|
+
chunk_size = self.put_stream_chunk_bytes
|
|
845
|
+
|
|
846
|
+
async def generator() -> AsyncIterator:
|
|
847
|
+
# First yield header
|
|
848
|
+
yield header_bytes
|
|
849
|
+
# Then yield data in chunks
|
|
850
|
+
offset = 0
|
|
851
|
+
while offset < kv_len:
|
|
852
|
+
next_offset = min(kv_len, offset + chunk_size)
|
|
853
|
+
yield kv_view[offset:next_offset]
|
|
854
|
+
offset = next_offset
|
|
855
|
+
|
|
856
|
+
return total_len, generator()
|
|
857
|
+
|
|
858
|
+
def support_batched_async_contains(self) -> bool:
|
|
859
|
+
"""Indicate support for batched async contains operation."""
|
|
860
|
+
return True
|
|
861
|
+
|
|
862
|
+
async def _batched_async_contains(
|
|
863
|
+
self, lookup_id: str, keys: List[CacheEngineKey], pin: bool = False
|
|
864
|
+
) -> int:
|
|
865
|
+
"""
|
|
866
|
+
Check existence of keys sequentially until first miss.
|
|
867
|
+
|
|
868
|
+
Args:
|
|
869
|
+
lookup_id: Lookup identifier (for logging/tracking)
|
|
870
|
+
keys: List of keys to check
|
|
871
|
+
pin: Whether to pin data (unused, for API compatibility)
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
Number of consecutive hits from the start
|
|
875
|
+
"""
|
|
876
|
+
num_hits = 0
|
|
877
|
+
for key in keys:
|
|
878
|
+
lease = None
|
|
879
|
+
try:
|
|
880
|
+
lease = await self._executor_submit_lease_acquisition(key)
|
|
881
|
+
if lease is None:
|
|
882
|
+
break
|
|
883
|
+
|
|
884
|
+
num_hits += 1
|
|
885
|
+
|
|
886
|
+
except Exception as exc:
|
|
887
|
+
logger.debug(f"Lease acquisition failed for {key}: {exc}")
|
|
888
|
+
break
|
|
889
|
+
finally:
|
|
890
|
+
if lease is not None:
|
|
891
|
+
await self._release_lease(key, lease.lease_id)
|
|
892
|
+
|
|
893
|
+
return num_hits
|
|
894
|
+
|
|
895
|
+
async def batched_async_contains(
|
|
896
|
+
self, lookup_id: str, keys: List[CacheEngineKey], pin: bool = False
|
|
897
|
+
) -> int:
|
|
898
|
+
"""Check existence of multiple keys (queued with priority)."""
|
|
899
|
+
return await self.pq_executor.submit_job(
|
|
900
|
+
self._batched_async_contains,
|
|
901
|
+
lookup_id=lookup_id,
|
|
902
|
+
keys=keys,
|
|
903
|
+
pin=pin,
|
|
904
|
+
priority=Priorities.LEASE,
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
def support_batched_get_non_blocking(self) -> bool:
|
|
908
|
+
"""Indicate support for non-blocking batched GET."""
|
|
909
|
+
return True
|
|
910
|
+
|
|
911
|
+
async def _batched_get_non_blocking(
|
|
912
|
+
self, lookup_id: str, keys: List[CacheEngineKey]
|
|
913
|
+
) -> List[MemoryObj]:
|
|
914
|
+
"""Prefetch multiple keys and filter out None results."""
|
|
915
|
+
results = await self.batched_get(keys)
|
|
916
|
+
return [r for r in results if r is not None]
|
|
917
|
+
|
|
918
|
+
async def batched_get_non_blocking(
|
|
919
|
+
self, lookup_id: str, keys: List[CacheEngineKey]
|
|
920
|
+
) -> List[MemoryObj]:
|
|
921
|
+
"""Prefetch multiple keys (queued with priority)."""
|
|
922
|
+
return await self.pq_executor.submit_job(
|
|
923
|
+
self._batched_get_non_blocking,
|
|
924
|
+
lookup_id=lookup_id,
|
|
925
|
+
keys=keys,
|
|
926
|
+
priority=Priorities.PREFETCH,
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
def support_batched_get(self) -> bool:
|
|
930
|
+
"""Indicate support for batched GET operations."""
|
|
931
|
+
return True
|
|
932
|
+
|
|
933
|
+
async def list(self) -> List[str]:
|
|
934
|
+
"""List operation not supported by ai-toolkit."""
|
|
935
|
+
return []
|
|
936
|
+
|
|
937
|
+
def remove_sync(self, key: CacheEngineKey) -> bool:
|
|
938
|
+
"""Remove operation not supported by ai-toolkit."""
|
|
939
|
+
return True
|
|
940
|
+
|
|
941
|
+
def support_ping(self) -> bool:
|
|
942
|
+
"""Indicate ping operation is not supported."""
|
|
943
|
+
return False
|
|
944
|
+
|
|
945
|
+
async def ping(self) -> int:
|
|
946
|
+
"""Ping operation not implemented."""
|
|
947
|
+
raise NotImplementedError(
|
|
948
|
+
"Ping operation not supported by SageMaker HyperPod connector"
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
async def close(self):
|
|
952
|
+
"""Clean up all resources and log statistics."""
|
|
953
|
+
# Log final statistics
|
|
954
|
+
logger.info(
|
|
955
|
+
f"SageMaker HyperPod Connector Statistics: "
|
|
956
|
+
f"GET(ok/fail)={self.stats['get_success']}/{self.stats['get_failure']}, "
|
|
957
|
+
f"PUT(ok/fail)={self.stats['put_success']}/{self.stats['put_failure']}, "
|
|
958
|
+
f"leases(acq/rel/fail)={self.stats['lease_acquired']}/"
|
|
959
|
+
f"{self.stats['lease_released']}/{self.stats['lease_release_failed']}"
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
# Shutdown priority queue executor
|
|
963
|
+
try:
|
|
964
|
+
await self.pq_executor.shutdown(wait=True)
|
|
965
|
+
except Exception as e:
|
|
966
|
+
logger.warning(f"Error shutting down executor: {e}")
|
|
967
|
+
|
|
968
|
+
# Close HTTP session
|
|
969
|
+
if self.http_session is not None:
|
|
970
|
+
try:
|
|
971
|
+
await self.http_session.close()
|
|
972
|
+
except Exception as e:
|
|
973
|
+
logger.warning(f"Error closing HTTP session: {e}")
|
|
974
|
+
self.http_session = None
|
|
975
|
+
|
|
976
|
+
# Release shared memory
|
|
977
|
+
if self.shared_memory_map is not None:
|
|
978
|
+
self.shared_memory_map = None
|
|
979
|
+
|
|
980
|
+
if self.shared_memory_obj is not None:
|
|
981
|
+
try:
|
|
982
|
+
self.shared_memory_obj.close()
|
|
983
|
+
except Exception as e:
|
|
984
|
+
logger.warning(f"Error closing shared memory object: {e}")
|
|
985
|
+
self.shared_memory_obj = None
|
|
986
|
+
|
|
987
|
+
logger.info("SageMaker HyperPod connector closed")
|