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,747 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
File-system based L2 adapter using aiofiles for async I/O.
|
|
4
|
+
|
|
5
|
+
Stores KV cache objects as raw tensor bytes on disk (no metadata
|
|
6
|
+
header). Each ObjectKey maps to a separate ``.data`` file whose
|
|
7
|
+
name encodes all key fields so it can be reversed on startup.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Future
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
# Standard
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
16
|
+
import asyncio
|
|
17
|
+
import os
|
|
18
|
+
import threading
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from lmcache.v1.distributed.internal_api import (
|
|
22
|
+
L1MemoryDesc,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Third Party
|
|
26
|
+
import aiofiles
|
|
27
|
+
import aiofiles.os
|
|
28
|
+
|
|
29
|
+
# First Party
|
|
30
|
+
from lmcache.logging import init_logger
|
|
31
|
+
from lmcache.native_storage_ops import Bitmap
|
|
32
|
+
from lmcache.v1.distributed.api import ObjectKey
|
|
33
|
+
from lmcache.v1.distributed.l2_adapters.base import (
|
|
34
|
+
L2AdapterInterface,
|
|
35
|
+
L2TaskId,
|
|
36
|
+
)
|
|
37
|
+
from lmcache.v1.distributed.l2_adapters.config import (
|
|
38
|
+
L2AdapterConfigBase,
|
|
39
|
+
register_l2_adapter_type,
|
|
40
|
+
)
|
|
41
|
+
from lmcache.v1.distributed.l2_adapters.factory import (
|
|
42
|
+
register_l2_adapter_factory,
|
|
43
|
+
)
|
|
44
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
45
|
+
|
|
46
|
+
logger = init_logger(__name__)
|
|
47
|
+
|
|
48
|
+
_KEY_SEP = "@"
|
|
49
|
+
# ``@`` in both ``model_name`` and ``cache_salt`` is rejected by
|
|
50
|
+
# ObjectKey.__post_init__, so splitting on ``@`` is unambiguous.
|
|
51
|
+
# Kept in sync with native_connector_l2_adapter.py and
|
|
52
|
+
# csrc/storage_backends/fs/connector.cpp.
|
|
53
|
+
_PATH_SLASH_REPLACEMENT = "-SEP-"
|
|
54
|
+
_FILE_EXT = ".data"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _readinto_full(
|
|
58
|
+
f, # typing: IO[bytes]
|
|
59
|
+
buf: Union[bytearray, memoryview, bytes],
|
|
60
|
+
) -> int:
|
|
61
|
+
"""Loop readinto() until *buf* is full or EOF.
|
|
62
|
+
|
|
63
|
+
A single ``readinto()`` may return fewer bytes than
|
|
64
|
+
*len(buf)* even when more data is available. This
|
|
65
|
+
helper keeps reading until the buffer is completely
|
|
66
|
+
filled or the file reaches EOF.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Total number of bytes read.
|
|
70
|
+
"""
|
|
71
|
+
mv = memoryview(buf) if not isinstance(buf, memoryview) else buf
|
|
72
|
+
total = 0
|
|
73
|
+
while total < len(mv):
|
|
74
|
+
n = f.readinto(mv[total:])
|
|
75
|
+
if n is None or n == 0:
|
|
76
|
+
break
|
|
77
|
+
total += n
|
|
78
|
+
return total
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def _async_readinto_full(
|
|
82
|
+
f, # aiofiles async file handle
|
|
83
|
+
buf: Union[bytearray, memoryview, bytes],
|
|
84
|
+
) -> int:
|
|
85
|
+
"""Async version of :func:`_readinto_full`."""
|
|
86
|
+
mv = memoryview(buf) if not isinstance(buf, memoryview) else buf
|
|
87
|
+
total = 0
|
|
88
|
+
while total < len(mv):
|
|
89
|
+
n = await f.readinto(mv[total:])
|
|
90
|
+
if n is None or n == 0:
|
|
91
|
+
break
|
|
92
|
+
total += n
|
|
93
|
+
return total
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _object_key_to_filename(key: ObjectKey) -> str:
|
|
97
|
+
"""Build a reversible, filesystem-safe filename.
|
|
98
|
+
|
|
99
|
+
Unsalted::
|
|
100
|
+
|
|
101
|
+
<safe_model>@0x<kv_rank_hex>@<chunk_hash_hex>.data
|
|
102
|
+
|
|
103
|
+
Salted (trailing ``cache_salt``)::
|
|
104
|
+
|
|
105
|
+
<safe_model>@0x<kv_rank_hex>@<chunk_hash_hex>@<cache_salt>.data
|
|
106
|
+
|
|
107
|
+
The 3-field unsalted shape is bit-identical to the pre-cache_salt
|
|
108
|
+
format, so existing un-salted cache directories remain valid and
|
|
109
|
+
no migration is needed.
|
|
110
|
+
|
|
111
|
+
``kv_rank`` is written in ``0x`` prefixed hex so each byte
|
|
112
|
+
of the bitmap ``(ws<<24)|(rank<<16)|(local_ws<<8)|local``
|
|
113
|
+
is directly readable.
|
|
114
|
+
"""
|
|
115
|
+
safe_model = key.model_name.replace("/", _PATH_SLASH_REPLACEMENT)
|
|
116
|
+
base = f"{safe_model}{_KEY_SEP}{key.kv_rank:#010x}{_KEY_SEP}{key.chunk_hash.hex()}"
|
|
117
|
+
if key.cache_salt:
|
|
118
|
+
return f"{base}{_KEY_SEP}{key.cache_salt}{_FILE_EXT}"
|
|
119
|
+
return f"{base}{_FILE_EXT}"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _filename_to_object_key(
|
|
123
|
+
filename: str,
|
|
124
|
+
) -> Optional[ObjectKey]:
|
|
125
|
+
"""Reverse ``_object_key_to_filename``.
|
|
126
|
+
|
|
127
|
+
Accepts both the 3-field unsalted shape and the 4-field salted
|
|
128
|
+
shape (trailing ``cache_salt``). Returns ``None`` for anything
|
|
129
|
+
else. Since ``model_name`` is guaranteed not to contain ``@``,
|
|
130
|
+
plain ``split`` suffices — no marker, no rsplit.
|
|
131
|
+
"""
|
|
132
|
+
if not filename.endswith(_FILE_EXT):
|
|
133
|
+
return None
|
|
134
|
+
stem = filename[: -len(_FILE_EXT)]
|
|
135
|
+
parts = stem.split(_KEY_SEP)
|
|
136
|
+
if len(parts) == 3:
|
|
137
|
+
safe_model, kv_rank_str, chunk_hash_hex = parts
|
|
138
|
+
cache_salt = ""
|
|
139
|
+
elif len(parts) == 4:
|
|
140
|
+
safe_model, kv_rank_str, chunk_hash_hex, cache_salt = parts
|
|
141
|
+
else:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
model_name = safe_model.replace(_PATH_SLASH_REPLACEMENT, "/")
|
|
145
|
+
try:
|
|
146
|
+
chunk_hash = bytes.fromhex(chunk_hash_hex)
|
|
147
|
+
kv_rank = int(kv_rank_str, 16)
|
|
148
|
+
# ObjectKey.__post_init__ raises ValueError when the decoded
|
|
149
|
+
# model_name / cache_salt violate the forbidden-char or length
|
|
150
|
+
# invariants (e.g. a stray file from another tool on disk).
|
|
151
|
+
# The contract here is to return None for anything unparsable,
|
|
152
|
+
# so keep the constructor inside the try block.
|
|
153
|
+
return ObjectKey(
|
|
154
|
+
chunk_hash=chunk_hash,
|
|
155
|
+
model_name=model_name,
|
|
156
|
+
kv_rank=kv_rank,
|
|
157
|
+
cache_salt=cache_salt,
|
|
158
|
+
)
|
|
159
|
+
except ValueError:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class FSL2AdapterConfig(L2AdapterConfigBase):
|
|
164
|
+
"""
|
|
165
|
+
Config for the filesystem-backed L2 adapter.
|
|
166
|
+
|
|
167
|
+
Fields:
|
|
168
|
+
- base_path: directory for storing KV cache files.
|
|
169
|
+
- relative_tmp_dir: optional relative sub-dir for
|
|
170
|
+
temp files (same as fs_connector_relative_tmp_dir).
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
base_path: str,
|
|
176
|
+
relative_tmp_dir: Optional[str] = None,
|
|
177
|
+
read_ahead_size: Optional[int] = None,
|
|
178
|
+
use_odirect: bool = False,
|
|
179
|
+
):
|
|
180
|
+
"""Initialize FSL2AdapterConfig.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
base_path: Directory for storing KV cache files.
|
|
184
|
+
relative_tmp_dir: Relative sub-dir under
|
|
185
|
+
base_path for temp files during writes.
|
|
186
|
+
read_ahead_size: If set, trigger filesystem
|
|
187
|
+
readahead by issuing a small initial read
|
|
188
|
+
of this many bytes before reading the rest.
|
|
189
|
+
use_odirect: If True, bypass the OS page cache
|
|
190
|
+
using O_DIRECT for both reads and writes.
|
|
191
|
+
Requires buffer sizes aligned to the
|
|
192
|
+
filesystem block size.
|
|
193
|
+
"""
|
|
194
|
+
self.base_path = base_path
|
|
195
|
+
self.relative_tmp_dir = relative_tmp_dir
|
|
196
|
+
self.read_ahead_size = read_ahead_size
|
|
197
|
+
self.use_odirect = use_odirect
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def from_dict(cls, d: dict) -> "FSL2AdapterConfig":
|
|
201
|
+
base_path = d.get("base_path")
|
|
202
|
+
if not isinstance(base_path, str) or not base_path:
|
|
203
|
+
raise ValueError("base_path must be a non-empty string")
|
|
204
|
+
relative_tmp_dir = d.get("relative_tmp_dir", None)
|
|
205
|
+
if relative_tmp_dir is not None:
|
|
206
|
+
if not isinstance(relative_tmp_dir, str):
|
|
207
|
+
raise ValueError("relative_tmp_dir must be a string")
|
|
208
|
+
read_ahead_size = d.get("read_ahead_size", None)
|
|
209
|
+
if read_ahead_size is not None:
|
|
210
|
+
if not isinstance(read_ahead_size, int) or read_ahead_size <= 0:
|
|
211
|
+
raise ValueError("read_ahead_size must be a positive integer")
|
|
212
|
+
use_odirect = d.get("use_odirect", False)
|
|
213
|
+
if not isinstance(use_odirect, bool):
|
|
214
|
+
raise ValueError("use_odirect must be a boolean")
|
|
215
|
+
return cls(
|
|
216
|
+
base_path=base_path,
|
|
217
|
+
relative_tmp_dir=relative_tmp_dir,
|
|
218
|
+
read_ahead_size=read_ahead_size,
|
|
219
|
+
use_odirect=use_odirect,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def help(cls) -> str:
|
|
224
|
+
return (
|
|
225
|
+
"FS L2 adapter config fields:\n"
|
|
226
|
+
"- base_path (str): directory for KV cache "
|
|
227
|
+
"files (required)\n"
|
|
228
|
+
"- relative_tmp_dir (str): relative sub-dir "
|
|
229
|
+
"for temp files (optional, same as "
|
|
230
|
+
"fs_connector_relative_tmp_dir)\n"
|
|
231
|
+
"- read_ahead_size (int): trigger fs "
|
|
232
|
+
"readahead by reading this many bytes first "
|
|
233
|
+
"(optional)\n"
|
|
234
|
+
"- use_odirect (bool): bypass page cache "
|
|
235
|
+
"via O_DIRECT (optional, default false)"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class FSL2Adapter(L2AdapterInterface):
|
|
240
|
+
"""
|
|
241
|
+
File-system backed L2 adapter with async I/O via *aiofiles*.
|
|
242
|
+
|
|
243
|
+
Each file stores **only** the raw tensor bytes (no metadata
|
|
244
|
+
header), which gives maximum I/O throughput. The file name
|
|
245
|
+
itself encodes the full ``ObjectKey`` so it is reversible.
|
|
246
|
+
|
|
247
|
+
Thread safety is ensured via a lock for shared bookkeeping
|
|
248
|
+
and an asyncio event loop running on a dedicated daemon
|
|
249
|
+
thread.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self, config: FSL2AdapterConfig):
|
|
253
|
+
super().__init__()
|
|
254
|
+
self._config = config
|
|
255
|
+
base = config.base_path
|
|
256
|
+
self._base_path = Path(base)
|
|
257
|
+
self._base_path.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
|
|
259
|
+
# Temp-file strategy aligned with FSConnector:
|
|
260
|
+
# if relative_tmp_dir is set, write to a sub-dir;
|
|
261
|
+
# otherwise fall back to a .tmp suffix.
|
|
262
|
+
self._relative_tmp_dir: Optional[Path] = None
|
|
263
|
+
if config.relative_tmp_dir is not None:
|
|
264
|
+
self._relative_tmp_dir = Path(config.relative_tmp_dir)
|
|
265
|
+
if (
|
|
266
|
+
self._relative_tmp_dir.is_absolute()
|
|
267
|
+
or ".." in self._relative_tmp_dir.parts
|
|
268
|
+
):
|
|
269
|
+
raise ValueError("Invalid relative_tmp_dir: " + config.relative_tmp_dir)
|
|
270
|
+
(self._base_path / self._relative_tmp_dir).mkdir(
|
|
271
|
+
parents=False, exist_ok=True
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# I/O tuning options aligned with FSConnector
|
|
275
|
+
self._read_ahead_size = config.read_ahead_size
|
|
276
|
+
self._use_odirect = config.use_odirect
|
|
277
|
+
self._os_disk_bs = 0
|
|
278
|
+
if self._use_odirect:
|
|
279
|
+
stat = os.statvfs(self._base_path)
|
|
280
|
+
self._os_disk_bs = stat.f_bsize
|
|
281
|
+
|
|
282
|
+
self._store_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
283
|
+
self._lookup_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
284
|
+
self._load_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
285
|
+
|
|
286
|
+
# Task bookkeeping
|
|
287
|
+
self._next_task_id: L2TaskId = 0
|
|
288
|
+
self._completed_store_tasks: dict[L2TaskId, bool] = {}
|
|
289
|
+
self._completed_lookup_tasks: dict[L2TaskId, Bitmap] = {}
|
|
290
|
+
self._completed_load_tasks: dict[L2TaskId, Bitmap] = {}
|
|
291
|
+
self._lock = threading.Lock()
|
|
292
|
+
|
|
293
|
+
# Background asyncio event loop
|
|
294
|
+
self._loop = asyncio.new_event_loop()
|
|
295
|
+
self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
|
296
|
+
self._loop_thread.start()
|
|
297
|
+
|
|
298
|
+
logger.info(
|
|
299
|
+
"Initialized FSL2Adapter with base_path=%s, "
|
|
300
|
+
"relative_tmp_dir=%s, "
|
|
301
|
+
"read_ahead_size=%s, use_odirect=%s",
|
|
302
|
+
self._base_path,
|
|
303
|
+
self._relative_tmp_dir,
|
|
304
|
+
self._read_ahead_size,
|
|
305
|
+
self._use_odirect,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# ------------------------------------------------------------------
|
|
309
|
+
# Event Fd Interface
|
|
310
|
+
# ------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
def get_store_event_fd(self) -> int:
|
|
313
|
+
return self._store_efd
|
|
314
|
+
|
|
315
|
+
def get_lookup_and_lock_event_fd(self) -> int:
|
|
316
|
+
return self._lookup_efd
|
|
317
|
+
|
|
318
|
+
def get_load_event_fd(self) -> int:
|
|
319
|
+
return self._load_efd
|
|
320
|
+
|
|
321
|
+
# ------------------------------------------------------------------
|
|
322
|
+
# Store Interface
|
|
323
|
+
# ------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
def submit_store_task(
|
|
326
|
+
self,
|
|
327
|
+
keys: list[ObjectKey],
|
|
328
|
+
objects: list[MemoryObj],
|
|
329
|
+
) -> L2TaskId:
|
|
330
|
+
with self._lock:
|
|
331
|
+
task_id = self._get_next_task_id()
|
|
332
|
+
|
|
333
|
+
asyncio.run_coroutine_threadsafe(
|
|
334
|
+
self._execute_store(keys, objects, task_id),
|
|
335
|
+
self._loop,
|
|
336
|
+
)
|
|
337
|
+
return task_id
|
|
338
|
+
|
|
339
|
+
def pop_completed_store_tasks(
|
|
340
|
+
self,
|
|
341
|
+
) -> dict[L2TaskId, bool]:
|
|
342
|
+
with self._lock:
|
|
343
|
+
completed = self._completed_store_tasks
|
|
344
|
+
self._completed_store_tasks = {}
|
|
345
|
+
return completed
|
|
346
|
+
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
# Lookup and Lock Interface
|
|
349
|
+
# ------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def submit_lookup_and_lock_task(self, keys: list[ObjectKey]) -> L2TaskId:
|
|
352
|
+
with self._lock:
|
|
353
|
+
task_id = self._get_next_task_id()
|
|
354
|
+
|
|
355
|
+
asyncio.run_coroutine_threadsafe(
|
|
356
|
+
self._execute_lookup(keys, task_id),
|
|
357
|
+
self._loop,
|
|
358
|
+
)
|
|
359
|
+
return task_id
|
|
360
|
+
|
|
361
|
+
def query_lookup_and_lock_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
362
|
+
with self._lock:
|
|
363
|
+
return self._completed_lookup_tasks.pop(task_id, None)
|
|
364
|
+
|
|
365
|
+
def submit_unlock(self, keys: list[ObjectKey]) -> None:
|
|
366
|
+
# No-op: FS adapter has no eviction, so locking
|
|
367
|
+
# between lookup and load is unnecessary.
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
# ------------------------------------------------------------------
|
|
371
|
+
# Load Interface
|
|
372
|
+
# ------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
def submit_load_task(
|
|
375
|
+
self,
|
|
376
|
+
keys: list[ObjectKey],
|
|
377
|
+
objects: list[MemoryObj],
|
|
378
|
+
) -> L2TaskId:
|
|
379
|
+
with self._lock:
|
|
380
|
+
task_id = self._get_next_task_id()
|
|
381
|
+
|
|
382
|
+
asyncio.run_coroutine_threadsafe(
|
|
383
|
+
self._execute_load(keys, objects, task_id),
|
|
384
|
+
self._loop,
|
|
385
|
+
)
|
|
386
|
+
return task_id
|
|
387
|
+
|
|
388
|
+
def query_load_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
389
|
+
with self._lock:
|
|
390
|
+
return self._completed_load_tasks.pop(task_id, None)
|
|
391
|
+
|
|
392
|
+
# ------------------------------------------------------------------
|
|
393
|
+
# Status Interface
|
|
394
|
+
# ------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
def report_status(self) -> dict:
|
|
397
|
+
"""Return a status dict for the FS L2 adapter."""
|
|
398
|
+
return {
|
|
399
|
+
"is_healthy": self._loop_thread.is_alive(),
|
|
400
|
+
"type": "FSL2Adapter",
|
|
401
|
+
"base_path": str(self._base_path),
|
|
402
|
+
"use_odirect": self._use_odirect,
|
|
403
|
+
"event_loop_alive": self._loop_thread.is_alive(),
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# ------------------------------------------------------------------
|
|
407
|
+
# Eviction Interface
|
|
408
|
+
# ------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
def delete(self, keys: list[ObjectKey]) -> None:
|
|
411
|
+
# Not implemented for the filesystem adapter.
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
def get_usage(self) -> tuple[float, float]:
|
|
415
|
+
# Not implemented for the filesystem adapter.
|
|
416
|
+
return (-1.0, -1.0)
|
|
417
|
+
|
|
418
|
+
# ------------------------------------------------------------------
|
|
419
|
+
# Cleanup
|
|
420
|
+
# ------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
def close(self) -> None:
|
|
423
|
+
async def _stop_tasks():
|
|
424
|
+
tasks = [
|
|
425
|
+
t
|
|
426
|
+
for t in asyncio.all_tasks(self._loop)
|
|
427
|
+
if t is not asyncio.current_task()
|
|
428
|
+
]
|
|
429
|
+
for task in tasks:
|
|
430
|
+
task.cancel()
|
|
431
|
+
if tasks:
|
|
432
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
433
|
+
|
|
434
|
+
if self._loop.is_running():
|
|
435
|
+
fut = asyncio.run_coroutine_threadsafe(_stop_tasks(), self._loop)
|
|
436
|
+
try:
|
|
437
|
+
fut.result(timeout=5)
|
|
438
|
+
except Exception:
|
|
439
|
+
pass
|
|
440
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
441
|
+
|
|
442
|
+
self._loop_thread.join()
|
|
443
|
+
self._loop.close()
|
|
444
|
+
|
|
445
|
+
os.close(self._store_efd)
|
|
446
|
+
os.close(self._lookup_efd)
|
|
447
|
+
os.close(self._load_efd)
|
|
448
|
+
logger.info("FSL2Adapter closed")
|
|
449
|
+
|
|
450
|
+
# ------------------------------------------------------------------
|
|
451
|
+
# Internal helpers
|
|
452
|
+
# ------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
def _run_event_loop(self) -> None:
|
|
455
|
+
asyncio.set_event_loop(self._loop)
|
|
456
|
+
self._loop.run_forever()
|
|
457
|
+
|
|
458
|
+
def _get_next_task_id(self) -> L2TaskId:
|
|
459
|
+
tid = self._next_task_id
|
|
460
|
+
self._next_task_id += 1
|
|
461
|
+
return tid
|
|
462
|
+
|
|
463
|
+
def _key_to_path(self, key: ObjectKey) -> Path:
|
|
464
|
+
return self._base_path / _object_key_to_filename(key)
|
|
465
|
+
|
|
466
|
+
async def _key_exists_on_disk(
|
|
467
|
+
self,
|
|
468
|
+
key: ObjectKey,
|
|
469
|
+
) -> bool:
|
|
470
|
+
"""Check whether the file for *key* exists on disk.
|
|
471
|
+
|
|
472
|
+
Uses ``aiofiles.os.path.exists`` so the check is
|
|
473
|
+
non-blocking and always reflects the real FS state,
|
|
474
|
+
which is critical for multi-node shared-FS setups.
|
|
475
|
+
"""
|
|
476
|
+
path = self._key_to_path(key)
|
|
477
|
+
return await aiofiles.os.path.exists(path)
|
|
478
|
+
|
|
479
|
+
def _key_to_file_and_tmp_path(self, key: ObjectKey) -> tuple[Path, Path]:
|
|
480
|
+
"""Return ``(final_path, tmp_path)``.
|
|
481
|
+
|
|
482
|
+
When ``relative_tmp_dir`` is configured, the temp file
|
|
483
|
+
is placed under that sub-directory (same behaviour as
|
|
484
|
+
``FSConnector._get_file_and_tmp_path``). Otherwise a
|
|
485
|
+
``.tmp`` suffix is used.
|
|
486
|
+
"""
|
|
487
|
+
fname = _object_key_to_filename(key)
|
|
488
|
+
final = self._base_path / fname
|
|
489
|
+
if self._relative_tmp_dir is not None:
|
|
490
|
+
tmp = self._base_path / self._relative_tmp_dir / fname
|
|
491
|
+
else:
|
|
492
|
+
tmp = final.with_suffix(".tmp")
|
|
493
|
+
return final, tmp
|
|
494
|
+
|
|
495
|
+
# ---- O_DIRECT helpers -----------------------------------------------
|
|
496
|
+
|
|
497
|
+
def _read_with_odirect(
|
|
498
|
+
self,
|
|
499
|
+
file_path: Path,
|
|
500
|
+
dst_buf: Union[bytearray, memoryview, bytes],
|
|
501
|
+
) -> int:
|
|
502
|
+
"""Synchronous O_DIRECT read into *dst_buf*.
|
|
503
|
+
|
|
504
|
+
Returns the number of bytes actually read.
|
|
505
|
+
Runs in an executor (not on the event loop).
|
|
506
|
+
"""
|
|
507
|
+
fd = -1
|
|
508
|
+
size = len(dst_buf)
|
|
509
|
+
try:
|
|
510
|
+
aligned = self._os_disk_bs > 0 and size % self._os_disk_bs == 0
|
|
511
|
+
if not aligned:
|
|
512
|
+
logger.warning(
|
|
513
|
+
"Cannot use O_DIRECT for %s, size is not aligned.",
|
|
514
|
+
file_path,
|
|
515
|
+
)
|
|
516
|
+
with open(file_path, "rb") as f:
|
|
517
|
+
return _readinto_full(f, dst_buf)
|
|
518
|
+
|
|
519
|
+
fd = os.open(
|
|
520
|
+
str(file_path),
|
|
521
|
+
os.O_RDONLY | getattr(os, "O_DIRECT", 0),
|
|
522
|
+
)
|
|
523
|
+
with os.fdopen(fd, "rb", buffering=0) as fdo:
|
|
524
|
+
fd = -1 # now managed by fdopen
|
|
525
|
+
return _readinto_full(fdo, dst_buf)
|
|
526
|
+
except Exception:
|
|
527
|
+
logger.exception("Failed to O_DIRECT read %s", file_path)
|
|
528
|
+
return 0
|
|
529
|
+
finally:
|
|
530
|
+
if fd >= 0:
|
|
531
|
+
try:
|
|
532
|
+
os.close(fd)
|
|
533
|
+
except OSError:
|
|
534
|
+
pass
|
|
535
|
+
|
|
536
|
+
def _write_with_odirect(self, file_path: Path, buf: bytes) -> None:
|
|
537
|
+
"""Synchronous O_DIRECT write of *buf*.
|
|
538
|
+
|
|
539
|
+
Runs in an executor (not on the event loop).
|
|
540
|
+
"""
|
|
541
|
+
fd = -1
|
|
542
|
+
try:
|
|
543
|
+
fd = os.open(
|
|
544
|
+
str(file_path),
|
|
545
|
+
os.O_CREAT | os.O_WRONLY | getattr(os, "O_DIRECT", 0),
|
|
546
|
+
0o644,
|
|
547
|
+
)
|
|
548
|
+
os.write(fd, buf)
|
|
549
|
+
except Exception:
|
|
550
|
+
logger.exception("Failed to O_DIRECT write %s", file_path)
|
|
551
|
+
raise
|
|
552
|
+
finally:
|
|
553
|
+
if fd >= 0:
|
|
554
|
+
try:
|
|
555
|
+
os.close(fd)
|
|
556
|
+
except OSError:
|
|
557
|
+
pass
|
|
558
|
+
|
|
559
|
+
# ---- store ----------------------------------------------------------
|
|
560
|
+
|
|
561
|
+
async def _execute_store(
|
|
562
|
+
self,
|
|
563
|
+
keys: list[ObjectKey],
|
|
564
|
+
objects: list[MemoryObj],
|
|
565
|
+
task_id: L2TaskId,
|
|
566
|
+
) -> None:
|
|
567
|
+
success = True
|
|
568
|
+
try:
|
|
569
|
+
for key, obj in zip(keys, objects, strict=True):
|
|
570
|
+
file_path, tmp_path = self._key_to_file_and_tmp_path(key)
|
|
571
|
+
|
|
572
|
+
# Skip if already stored on disk
|
|
573
|
+
if await aiofiles.os.path.exists(file_path):
|
|
574
|
+
continue
|
|
575
|
+
buf = obj.byte_array
|
|
576
|
+
size = len(buf)
|
|
577
|
+
|
|
578
|
+
try:
|
|
579
|
+
# Decide whether O_DIRECT is usable
|
|
580
|
+
do_odirect = self._use_odirect
|
|
581
|
+
if do_odirect:
|
|
582
|
+
aligned = self._os_disk_bs > 0 and size % self._os_disk_bs == 0
|
|
583
|
+
if not aligned:
|
|
584
|
+
logger.warning(
|
|
585
|
+
"Cannot use O_DIRECT for "
|
|
586
|
+
"writing size %d, not "
|
|
587
|
+
"aligned to block size "
|
|
588
|
+
"%d.",
|
|
589
|
+
size,
|
|
590
|
+
self._os_disk_bs,
|
|
591
|
+
)
|
|
592
|
+
do_odirect = False
|
|
593
|
+
|
|
594
|
+
if do_odirect:
|
|
595
|
+
await self._loop.run_in_executor(
|
|
596
|
+
None,
|
|
597
|
+
self._write_with_odirect,
|
|
598
|
+
tmp_path,
|
|
599
|
+
buf,
|
|
600
|
+
)
|
|
601
|
+
else:
|
|
602
|
+
async with aiofiles.open(tmp_path, "wb") as f:
|
|
603
|
+
await f.write(buf)
|
|
604
|
+
|
|
605
|
+
await aiofiles.os.replace(tmp_path, file_path)
|
|
606
|
+
logger.debug(
|
|
607
|
+
"FSL2Adapter stored key %s (%d bytes)",
|
|
608
|
+
file_path.name,
|
|
609
|
+
size,
|
|
610
|
+
)
|
|
611
|
+
except Exception:
|
|
612
|
+
logger.exception(
|
|
613
|
+
"FSL2Adapter failed to store %s",
|
|
614
|
+
file_path,
|
|
615
|
+
)
|
|
616
|
+
if await aiofiles.os.path.exists(tmp_path):
|
|
617
|
+
await aiofiles.os.unlink(tmp_path)
|
|
618
|
+
success = False
|
|
619
|
+
except Exception:
|
|
620
|
+
logger.exception(
|
|
621
|
+
"FSL2Adapter store task %s failed",
|
|
622
|
+
task_id,
|
|
623
|
+
)
|
|
624
|
+
success = False
|
|
625
|
+
|
|
626
|
+
with self._lock:
|
|
627
|
+
self._completed_store_tasks[task_id] = success
|
|
628
|
+
os.eventfd_write(self._store_efd, 1)
|
|
629
|
+
|
|
630
|
+
# ---- lookup ---------------------------------------------------------
|
|
631
|
+
|
|
632
|
+
async def _execute_lookup(
|
|
633
|
+
self,
|
|
634
|
+
keys: list[ObjectKey],
|
|
635
|
+
task_id: L2TaskId,
|
|
636
|
+
) -> None:
|
|
637
|
+
bitmap = Bitmap(len(keys))
|
|
638
|
+
for i, key in enumerate(keys):
|
|
639
|
+
if not await self._key_exists_on_disk(key):
|
|
640
|
+
continue
|
|
641
|
+
bitmap.set(i)
|
|
642
|
+
|
|
643
|
+
with self._lock:
|
|
644
|
+
self._completed_lookup_tasks[task_id] = bitmap
|
|
645
|
+
os.eventfd_write(self._lookup_efd, 1)
|
|
646
|
+
|
|
647
|
+
# ---- load -----------------------------------------------------------
|
|
648
|
+
|
|
649
|
+
async def _execute_load(
|
|
650
|
+
self,
|
|
651
|
+
keys: list[ObjectKey],
|
|
652
|
+
objects: list[MemoryObj],
|
|
653
|
+
task_id: L2TaskId,
|
|
654
|
+
) -> None:
|
|
655
|
+
bitmap = Bitmap(len(keys))
|
|
656
|
+
for i, key in enumerate(keys):
|
|
657
|
+
file_path = self._key_to_path(key)
|
|
658
|
+
try:
|
|
659
|
+
dst_buf = objects[i].byte_array
|
|
660
|
+
expected = len(dst_buf)
|
|
661
|
+
num_read: Optional[int] = None
|
|
662
|
+
|
|
663
|
+
# O_DIRECT path (sync, via executor)
|
|
664
|
+
if self._use_odirect:
|
|
665
|
+
num_read = await self._loop.run_in_executor(
|
|
666
|
+
None,
|
|
667
|
+
self._read_with_odirect,
|
|
668
|
+
file_path,
|
|
669
|
+
dst_buf,
|
|
670
|
+
)
|
|
671
|
+
if num_read != expected:
|
|
672
|
+
logger.warning(
|
|
673
|
+
"Incomplete O_DIRECT read for %s: expected %d, got %d",
|
|
674
|
+
file_path.name,
|
|
675
|
+
expected,
|
|
676
|
+
num_read or 0,
|
|
677
|
+
)
|
|
678
|
+
else:
|
|
679
|
+
bitmap.set(i)
|
|
680
|
+
logger.debug(
|
|
681
|
+
"FSL2Adapter loaded key %s (%d bytes, O_DIRECT)",
|
|
682
|
+
file_path.name,
|
|
683
|
+
num_read,
|
|
684
|
+
)
|
|
685
|
+
continue
|
|
686
|
+
|
|
687
|
+
# Standard async path with optional
|
|
688
|
+
# read-ahead
|
|
689
|
+
expected = len(dst_buf)
|
|
690
|
+
async with aiofiles.open(file_path, "rb") as f:
|
|
691
|
+
if self._read_ahead_size is None:
|
|
692
|
+
num_read = await _async_readinto_full(f, dst_buf)
|
|
693
|
+
else:
|
|
694
|
+
if not isinstance(dst_buf, memoryview):
|
|
695
|
+
dst_buf = memoryview(dst_buf)
|
|
696
|
+
# Trigger readahead with a
|
|
697
|
+
# small initial read
|
|
698
|
+
ra = self._read_ahead_size
|
|
699
|
+
n_head = await _async_readinto_full(f, dst_buf[:ra])
|
|
700
|
+
if n_head == ra:
|
|
701
|
+
n_tail = await _async_readinto_full(f, dst_buf[ra:])
|
|
702
|
+
num_read = n_head + n_tail
|
|
703
|
+
else:
|
|
704
|
+
num_read = n_head
|
|
705
|
+
|
|
706
|
+
if num_read != expected:
|
|
707
|
+
logger.warning(
|
|
708
|
+
"Incomplete read for %s: expected %d, got %d",
|
|
709
|
+
file_path.name,
|
|
710
|
+
expected,
|
|
711
|
+
num_read,
|
|
712
|
+
)
|
|
713
|
+
continue
|
|
714
|
+
|
|
715
|
+
bitmap.set(i)
|
|
716
|
+
logger.debug(
|
|
717
|
+
"FSL2Adapter loaded key %s (%d bytes)",
|
|
718
|
+
file_path.name,
|
|
719
|
+
num_read,
|
|
720
|
+
)
|
|
721
|
+
except FileNotFoundError:
|
|
722
|
+
continue
|
|
723
|
+
except Exception:
|
|
724
|
+
logger.exception(
|
|
725
|
+
"FSL2Adapter failed to load %s",
|
|
726
|
+
file_path,
|
|
727
|
+
)
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
with self._lock:
|
|
731
|
+
self._completed_load_tasks[task_id] = bitmap
|
|
732
|
+
os.eventfd_write(self._load_efd, 1)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
# Self-register config type and adapter factory
|
|
736
|
+
register_l2_adapter_type("fs", FSL2AdapterConfig)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _create_fs_adapter(
|
|
740
|
+
config: L2AdapterConfigBase,
|
|
741
|
+
l1_memory_desc: "Optional[L1MemoryDesc]" = None,
|
|
742
|
+
) -> L2AdapterInterface:
|
|
743
|
+
"""Create an FSL2Adapter from config."""
|
|
744
|
+
return FSL2Adapter(config) # type: ignore[arg-type]
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
register_l2_adapter_factory("fs", _create_fs_adapter)
|