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,831 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Dynamic-file-mode Nixl L2 adapter.
|
|
4
|
+
|
|
5
|
+
Unlike the static ``NixlStoreL2Adapter`` which pre-allocates all storage
|
|
6
|
+
files at init time, this adapter opens/registers files per operation.
|
|
7
|
+
|
|
8
|
+
Atomic publish:
|
|
9
|
+
- Stores DMA-write to a per-operation ``<final_path>.tmp.<uuid>`` and
|
|
10
|
+
atomically ``rename()`` to the final deterministic path on completion.
|
|
11
|
+
This guarantees that readers (including other processes sharing the
|
|
12
|
+
same directory) never observe a partially-written file.
|
|
13
|
+
|
|
14
|
+
Persist (enabled by default via ``persist_enabled``, can be opted out):
|
|
15
|
+
- Keeps data files on disk at shutdown (no metadata dump).
|
|
16
|
+
|
|
17
|
+
Secondary lookup (always on):
|
|
18
|
+
- Lookup always checks secondary storage (disk) on miss and lazily
|
|
19
|
+
populates the in-memory index when a file is found. File names are
|
|
20
|
+
derived deterministically from ObjectKey.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Future
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
# Standard
|
|
27
|
+
from typing import Optional
|
|
28
|
+
import asyncio
|
|
29
|
+
import os
|
|
30
|
+
import threading
|
|
31
|
+
import uuid
|
|
32
|
+
|
|
33
|
+
# Third Party
|
|
34
|
+
from nixl._api import nixl_agent as NixlAgent
|
|
35
|
+
from nixl._api import nixl_agent_config as NixlAgentConfig
|
|
36
|
+
from nixl._api import (
|
|
37
|
+
nixlBind,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# First Party
|
|
41
|
+
from lmcache.logging import init_logger
|
|
42
|
+
from lmcache.native_storage_ops import Bitmap
|
|
43
|
+
from lmcache.v1.distributed.api import MemoryLayoutDesc, ObjectKey
|
|
44
|
+
from lmcache.v1.distributed.internal_api import L1MemoryDesc
|
|
45
|
+
from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
|
|
46
|
+
from lmcache.v1.distributed.l2_adapters.config import (
|
|
47
|
+
L2AdapterConfigBase,
|
|
48
|
+
register_l2_adapter_type,
|
|
49
|
+
)
|
|
50
|
+
from lmcache.v1.distributed.l2_adapters.factory import (
|
|
51
|
+
register_l2_adapter_factory,
|
|
52
|
+
)
|
|
53
|
+
from lmcache.v1.distributed.l2_adapters.nixl_store_l2_adapter import (
|
|
54
|
+
NixlStoreObj,
|
|
55
|
+
)
|
|
56
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
57
|
+
|
|
58
|
+
logger = init_logger(__name__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------
|
|
62
|
+
# ObjectKey <-> file path helpers
|
|
63
|
+
# ---------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _object_key_to_filename(key: ObjectKey) -> str:
|
|
67
|
+
"""Derive a deterministic file name from an ObjectKey.
|
|
68
|
+
|
|
69
|
+
Replaces ``/`` in model names with ``--`` to avoid creating
|
|
70
|
+
subdirectories (e.g. ``meta-llama/Llama-3-8B`` becomes
|
|
71
|
+
``meta-llama--Llama-3-8B``).
|
|
72
|
+
"""
|
|
73
|
+
safe_model_name = key.model_name.replace("/", "--")
|
|
74
|
+
chunk_hex = key.chunk_hash.hex()
|
|
75
|
+
return f"{safe_model_name}_{key.kv_rank:08x}_{chunk_hex}.bin"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------
|
|
79
|
+
# Dynamic Nixl storage agent
|
|
80
|
+
# ---------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class DynamicNixlStorageAgent:
|
|
84
|
+
"""Nixl storage agent that opens/registers files per operation.
|
|
85
|
+
|
|
86
|
+
The L1 memory handler is registered once at init (same as the static
|
|
87
|
+
agent). Storage files are registered on-demand for each store/load
|
|
88
|
+
and deregistered immediately after the transfer completes.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
device: str,
|
|
94
|
+
backend: str,
|
|
95
|
+
backend_params: dict[str, str],
|
|
96
|
+
l1_memory_desc: L1MemoryDesc,
|
|
97
|
+
):
|
|
98
|
+
self.backend = backend
|
|
99
|
+
self.device = device
|
|
100
|
+
self.backend_params = backend_params
|
|
101
|
+
self.l1_align_bytes = l1_memory_desc.align_bytes
|
|
102
|
+
self.file_path = backend_params["file_path"]
|
|
103
|
+
self.use_direct_io = (
|
|
104
|
+
str(backend_params.get("use_direct_io", "false")).lower() == "true"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self.agent_name = "DynNixlAgent_" + str(uuid.uuid4())
|
|
108
|
+
nixl_conf = NixlAgentConfig(backends=[])
|
|
109
|
+
self.nixl_agent = NixlAgent(self.agent_name, nixl_conf)
|
|
110
|
+
self.nixl_agent.create_backend(backend, backend_params)
|
|
111
|
+
|
|
112
|
+
# Register L1 memory (same as static agent)
|
|
113
|
+
self._init_mem_handlers(
|
|
114
|
+
device,
|
|
115
|
+
l1_memory_desc.ptr,
|
|
116
|
+
l1_memory_desc.size,
|
|
117
|
+
l1_memory_desc.align_bytes,
|
|
118
|
+
device_id=0,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# ---- L1 memory registration (one-time) ----
|
|
122
|
+
|
|
123
|
+
def _init_mem_handlers(self, device, buffer_ptr, buffer_size, page_size, device_id):
|
|
124
|
+
reg_list = [(buffer_ptr, buffer_size, device_id, "")]
|
|
125
|
+
xfer_desc = [
|
|
126
|
+
(base_addr, page_size, device_id)
|
|
127
|
+
for base_addr in range(buffer_ptr, buffer_ptr + buffer_size, page_size)
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
mem_type = "DRAM" if device == "cpu" else "VRAM"
|
|
131
|
+
|
|
132
|
+
self.mem_reg_descs = self.nixl_agent.register_memory(
|
|
133
|
+
reg_list, mem_type=mem_type
|
|
134
|
+
)
|
|
135
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type=mem_type)
|
|
136
|
+
self.mem_xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
137
|
+
"", xfer_descs, mem_type=mem_type
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# ---- Per-operation file helpers ----
|
|
141
|
+
|
|
142
|
+
def _open_flags(self, create: bool) -> int:
|
|
143
|
+
"""Return os.open flags for storage files."""
|
|
144
|
+
flags = os.O_RDWR
|
|
145
|
+
if create:
|
|
146
|
+
# O_TRUNC ensures any orphaned file from a previous crash
|
|
147
|
+
# is truncated, avoiding stale trailing bytes on disk.
|
|
148
|
+
flags |= os.O_CREAT | os.O_TRUNC
|
|
149
|
+
if self.use_direct_io and hasattr(os, "O_DIRECT"):
|
|
150
|
+
flags |= os.O_DIRECT
|
|
151
|
+
return flags
|
|
152
|
+
|
|
153
|
+
def _register_single_file(self, fd: int, file_size: int, page_size: int):
|
|
154
|
+
"""Register a single file with nixl and return (reg_descs, xfer_handler).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Tuple of (reg_descs, xfer_handler) for later cleanup.
|
|
158
|
+
"""
|
|
159
|
+
num_pages = file_size // page_size
|
|
160
|
+
|
|
161
|
+
reg_list = [(0, file_size, fd, "")]
|
|
162
|
+
xfer_desc = [(offset * page_size, page_size, fd) for offset in range(num_pages)]
|
|
163
|
+
|
|
164
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type="FILE")
|
|
165
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type="FILE")
|
|
166
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
167
|
+
self.agent_name, xfer_descs, mem_type="FILE"
|
|
168
|
+
)
|
|
169
|
+
return reg_descs, xfer_handler
|
|
170
|
+
|
|
171
|
+
def _deregister_file(self, reg_descs, xfer_handler):
|
|
172
|
+
"""Deregister a file from nixl."""
|
|
173
|
+
self.nixl_agent.release_dlist_handle(xfer_handler)
|
|
174
|
+
self.nixl_agent.deregister_memory(reg_descs)
|
|
175
|
+
|
|
176
|
+
async def dynamic_store_file(
|
|
177
|
+
self,
|
|
178
|
+
mem_indices: list[int],
|
|
179
|
+
file_path: str,
|
|
180
|
+
page_size: int,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Write-to-temp-then-rename to publish the final file atomically.
|
|
183
|
+
|
|
184
|
+
The DMA write goes to ``<file_path>.tmp.<uuid>`` in the same
|
|
185
|
+
directory. Only after the transfer completes successfully is the
|
|
186
|
+
temp file atomically renamed to the final path, ensuring that
|
|
187
|
+
concurrent readers (including other processes sharing the same
|
|
188
|
+
directory) never observe a partially-written file.
|
|
189
|
+
"""
|
|
190
|
+
file_size = len(mem_indices) * page_size
|
|
191
|
+
tmp_path = f"{file_path}.tmp.{uuid.uuid4().hex}"
|
|
192
|
+
fd = os.open(tmp_path, self._open_flags(create=True))
|
|
193
|
+
try:
|
|
194
|
+
reg_descs, xfer_handler = self._register_single_file(
|
|
195
|
+
fd, file_size, page_size
|
|
196
|
+
)
|
|
197
|
+
try:
|
|
198
|
+
storage_indices = list(range(len(mem_indices)))
|
|
199
|
+
handle = self.nixl_agent.make_prepped_xfer(
|
|
200
|
+
"WRITE",
|
|
201
|
+
self.mem_xfer_handler,
|
|
202
|
+
mem_indices,
|
|
203
|
+
xfer_handler,
|
|
204
|
+
storage_indices,
|
|
205
|
+
)
|
|
206
|
+
await self._post_non_blocking(handle)
|
|
207
|
+
self.nixl_agent.release_xfer_handle(handle)
|
|
208
|
+
finally:
|
|
209
|
+
self._deregister_file(reg_descs, xfer_handler)
|
|
210
|
+
except BaseException:
|
|
211
|
+
# Best-effort cleanup of the temp file on failure.
|
|
212
|
+
try:
|
|
213
|
+
os.unlink(tmp_path)
|
|
214
|
+
except FileNotFoundError:
|
|
215
|
+
pass
|
|
216
|
+
raise
|
|
217
|
+
finally:
|
|
218
|
+
os.close(fd)
|
|
219
|
+
|
|
220
|
+
# Atomic publish: readers only ever see a complete file at file_path.
|
|
221
|
+
# TODO(Jiayi): Only guaranteed to be atomic within the local posix filesystems.
|
|
222
|
+
os.rename(tmp_path, file_path)
|
|
223
|
+
|
|
224
|
+
async def dynamic_load_file(
|
|
225
|
+
self,
|
|
226
|
+
mem_indices: list[int],
|
|
227
|
+
file_path: str,
|
|
228
|
+
page_size: int,
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Open an existing file, DMA read into L1 memory, then clean up."""
|
|
231
|
+
file_size = len(mem_indices) * page_size
|
|
232
|
+
fd = os.open(file_path, self._open_flags(create=False))
|
|
233
|
+
try:
|
|
234
|
+
reg_descs, xfer_handler = self._register_single_file(
|
|
235
|
+
fd, file_size, page_size
|
|
236
|
+
)
|
|
237
|
+
try:
|
|
238
|
+
storage_indices = list(range(len(mem_indices)))
|
|
239
|
+
handle = self.nixl_agent.make_prepped_xfer(
|
|
240
|
+
"READ",
|
|
241
|
+
self.mem_xfer_handler,
|
|
242
|
+
mem_indices,
|
|
243
|
+
xfer_handler,
|
|
244
|
+
storage_indices,
|
|
245
|
+
)
|
|
246
|
+
await self._post_non_blocking(handle)
|
|
247
|
+
self.nixl_agent.release_xfer_handle(handle)
|
|
248
|
+
finally:
|
|
249
|
+
self._deregister_file(reg_descs, xfer_handler)
|
|
250
|
+
finally:
|
|
251
|
+
os.close(fd)
|
|
252
|
+
|
|
253
|
+
def dynamic_delete_file(self, file_path: str) -> None:
|
|
254
|
+
"""Delete a storage file from disk."""
|
|
255
|
+
try:
|
|
256
|
+
os.unlink(file_path)
|
|
257
|
+
except FileNotFoundError:
|
|
258
|
+
logger.warning("File already deleted: %s", file_path)
|
|
259
|
+
|
|
260
|
+
# ---- Shared helpers ----
|
|
261
|
+
|
|
262
|
+
def get_memory_indices(self, raw_addr: int, mem_size: int) -> list[int]:
|
|
263
|
+
"""Get L1 memory page indices for the given address and size."""
|
|
264
|
+
if raw_addr % self.l1_align_bytes != 0:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"Raw address {raw_addr} is not aligned to "
|
|
267
|
+
f"page size {self.l1_align_bytes}"
|
|
268
|
+
)
|
|
269
|
+
if mem_size % self.l1_align_bytes != 0:
|
|
270
|
+
raise ValueError(
|
|
271
|
+
f"Memory size {mem_size} is not a multiple of "
|
|
272
|
+
f"page size {self.l1_align_bytes}"
|
|
273
|
+
)
|
|
274
|
+
num_pages = mem_size // self.l1_align_bytes
|
|
275
|
+
return [(raw_addr // self.l1_align_bytes + i) for i in range(num_pages)]
|
|
276
|
+
|
|
277
|
+
def get_file_path_for_key(self, key: ObjectKey) -> str:
|
|
278
|
+
"""Return the full file path for a given ObjectKey."""
|
|
279
|
+
return os.path.join(self.file_path, _object_key_to_filename(key))
|
|
280
|
+
|
|
281
|
+
async def _post_non_blocking(self, handle):
|
|
282
|
+
"""Await a nixl transfer until done."""
|
|
283
|
+
state = self.nixl_agent.transfer(handle)
|
|
284
|
+
while state != "DONE" and state != "ERR":
|
|
285
|
+
try:
|
|
286
|
+
state = self.nixl_agent.check_xfer_state(handle)
|
|
287
|
+
except nixlBind.nixlBackendError:
|
|
288
|
+
raise
|
|
289
|
+
await asyncio.sleep(0.01)
|
|
290
|
+
if state == "ERR":
|
|
291
|
+
raise RuntimeError("NIXL transfer failed")
|
|
292
|
+
|
|
293
|
+
def cleanup_temp_files(self) -> None:
|
|
294
|
+
"""Remove leftover ``*.tmp.*`` files in the storage directory.
|
|
295
|
+
|
|
296
|
+
These can be left behind if a store crashed between opening the
|
|
297
|
+
temp file and the atomic rename. Called at shutdown as a best-effort
|
|
298
|
+
GC; orphans don't affect correctness because they're never matched
|
|
299
|
+
by the deterministic ``ObjectKey → filename`` mapping.
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
entries = os.listdir(self.file_path)
|
|
303
|
+
except FileNotFoundError:
|
|
304
|
+
return
|
|
305
|
+
for name in entries:
|
|
306
|
+
# Temp suffix format: "<final_name>.tmp.<hex>"
|
|
307
|
+
if ".tmp." in name:
|
|
308
|
+
try:
|
|
309
|
+
os.unlink(os.path.join(self.file_path, name))
|
|
310
|
+
except FileNotFoundError:
|
|
311
|
+
pass
|
|
312
|
+
except OSError as e:
|
|
313
|
+
logger.warning(
|
|
314
|
+
"Failed to remove leftover temp file %s: %s", name, e
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def close(self):
|
|
318
|
+
"""Release L1 memory handlers."""
|
|
319
|
+
self.nixl_agent.release_dlist_handle(self.mem_xfer_handler)
|
|
320
|
+
self.nixl_agent.deregister_memory(self.mem_reg_descs)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# ---------------------------------------------------------------
|
|
324
|
+
# Dynamic L2 adapter
|
|
325
|
+
# ---------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class DynamicNixlStoreL2Adapter(L2AdapterInterface):
|
|
329
|
+
"""Nixl L2 adapter using dynamic per-operation file registration.
|
|
330
|
+
|
|
331
|
+
Each store creates a new file on disk; each load re-opens the file.
|
|
332
|
+
|
|
333
|
+
When ``persist_enabled`` is True (the default), data files are kept
|
|
334
|
+
on disk at shutdown. Lookup always checks secondary storage (disk)
|
|
335
|
+
for keys not in the in-memory index and populates the index lazily.
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
def __init__(
|
|
339
|
+
self,
|
|
340
|
+
config: DynamicNixlStoreL2AdapterConfig,
|
|
341
|
+
l1_memory_desc: L1MemoryDesc,
|
|
342
|
+
):
|
|
343
|
+
super().__init__()
|
|
344
|
+
self._config = config
|
|
345
|
+
|
|
346
|
+
self._store_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
347
|
+
self._lookup_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
348
|
+
self._load_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
349
|
+
|
|
350
|
+
# Cache data structures
|
|
351
|
+
self._memory_objects: dict[ObjectKey, NixlStoreObj] = {}
|
|
352
|
+
self._inflight_stores: set[ObjectKey] = set()
|
|
353
|
+
self._total_bytes: int = 0
|
|
354
|
+
max_capacity_gb = float(config.backend_params.get("max_capacity_gb", 0))
|
|
355
|
+
if max_capacity_gb <= 0:
|
|
356
|
+
raise ValueError("backend_params must include a positive 'max_capacity_gb'")
|
|
357
|
+
self._max_capacity_bytes: int = int(max_capacity_gb * (1024**3))
|
|
358
|
+
|
|
359
|
+
# Task ID management
|
|
360
|
+
self._next_task_id: L2TaskId = 0
|
|
361
|
+
self._completed_store_tasks: dict[L2TaskId, bool] = {}
|
|
362
|
+
self._completed_lookup_tasks: dict[L2TaskId, Bitmap] = {}
|
|
363
|
+
self._completed_load_tasks: dict[L2TaskId, Bitmap] = {}
|
|
364
|
+
self._lock = threading.Lock()
|
|
365
|
+
|
|
366
|
+
# Asyncio event loop running in a background thread
|
|
367
|
+
self._loop = asyncio.new_event_loop()
|
|
368
|
+
self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
|
369
|
+
self._loop_thread.start()
|
|
370
|
+
|
|
371
|
+
# Initialize dynamic Nixl agent (L1 memory only, no pre-allocated files)
|
|
372
|
+
self.nixl_agent = DynamicNixlStorageAgent(
|
|
373
|
+
device="cpu",
|
|
374
|
+
backend=config.backend,
|
|
375
|
+
backend_params=config.backend_params,
|
|
376
|
+
l1_memory_desc=l1_memory_desc,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
self._persist_enabled = config.persist_config.persist_enabled
|
|
380
|
+
|
|
381
|
+
# --------------------
|
|
382
|
+
# Event Fd Interface
|
|
383
|
+
# --------------------
|
|
384
|
+
|
|
385
|
+
def get_store_event_fd(self) -> int:
|
|
386
|
+
return self._store_efd
|
|
387
|
+
|
|
388
|
+
def get_lookup_and_lock_event_fd(self) -> int:
|
|
389
|
+
return self._lookup_efd
|
|
390
|
+
|
|
391
|
+
def get_load_event_fd(self) -> int:
|
|
392
|
+
return self._load_efd
|
|
393
|
+
|
|
394
|
+
#####################
|
|
395
|
+
# Store Interface
|
|
396
|
+
#####################
|
|
397
|
+
|
|
398
|
+
def submit_store_task(
|
|
399
|
+
self,
|
|
400
|
+
keys: list[ObjectKey],
|
|
401
|
+
objects: list[MemoryObj],
|
|
402
|
+
) -> L2TaskId:
|
|
403
|
+
with self._lock:
|
|
404
|
+
task_id = self._get_next_task_id()
|
|
405
|
+
|
|
406
|
+
asyncio.run_coroutine_threadsafe(
|
|
407
|
+
self._execute_store_in_the_loop(keys, objects, task_id), self._loop
|
|
408
|
+
)
|
|
409
|
+
return task_id
|
|
410
|
+
|
|
411
|
+
def pop_completed_store_tasks(self) -> dict[L2TaskId, bool]:
|
|
412
|
+
with self._lock:
|
|
413
|
+
completed = self._completed_store_tasks
|
|
414
|
+
self._completed_store_tasks = {}
|
|
415
|
+
return completed
|
|
416
|
+
|
|
417
|
+
#####################
|
|
418
|
+
# Lookup and Lock Interface
|
|
419
|
+
#####################
|
|
420
|
+
|
|
421
|
+
def submit_lookup_and_lock_task(self, keys: list[ObjectKey]) -> L2TaskId:
|
|
422
|
+
with self._lock:
|
|
423
|
+
task_id = self._get_next_task_id()
|
|
424
|
+
|
|
425
|
+
self._loop.call_soon_threadsafe(self._execute_lookup_in_the_loop, keys, task_id)
|
|
426
|
+
return task_id
|
|
427
|
+
|
|
428
|
+
def query_lookup_and_lock_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
429
|
+
with self._lock:
|
|
430
|
+
return self._completed_lookup_tasks.pop(task_id, None)
|
|
431
|
+
|
|
432
|
+
def submit_unlock(self, keys: list[ObjectKey]) -> None:
|
|
433
|
+
def _unlock_keys(keys: list[ObjectKey]) -> None:
|
|
434
|
+
for key in keys:
|
|
435
|
+
if (obj := self._memory_objects.get(key)) is not None:
|
|
436
|
+
obj.decrease_pin_count()
|
|
437
|
+
|
|
438
|
+
self._loop.call_soon_threadsafe(_unlock_keys, keys)
|
|
439
|
+
|
|
440
|
+
#####################
|
|
441
|
+
# Load Interface
|
|
442
|
+
#####################
|
|
443
|
+
|
|
444
|
+
def submit_load_task(
|
|
445
|
+
self,
|
|
446
|
+
keys: list[ObjectKey],
|
|
447
|
+
objects: list[MemoryObj],
|
|
448
|
+
) -> L2TaskId:
|
|
449
|
+
with self._lock:
|
|
450
|
+
task_id = self._get_next_task_id()
|
|
451
|
+
|
|
452
|
+
asyncio.run_coroutine_threadsafe(
|
|
453
|
+
self._execute_load_in_loop(keys, objects, task_id), self._loop
|
|
454
|
+
)
|
|
455
|
+
return task_id
|
|
456
|
+
|
|
457
|
+
def query_load_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
458
|
+
with self._lock:
|
|
459
|
+
return self._completed_load_tasks.pop(task_id, None)
|
|
460
|
+
|
|
461
|
+
#####################
|
|
462
|
+
# Eviction Interface
|
|
463
|
+
#####################
|
|
464
|
+
|
|
465
|
+
def delete(self, keys: list[ObjectKey]) -> None:
|
|
466
|
+
"""Delete objects from storage, removing their files from disk."""
|
|
467
|
+
to_delete: list[tuple[ObjectKey, str]] = []
|
|
468
|
+
with self._lock:
|
|
469
|
+
for key in keys:
|
|
470
|
+
obj = self._memory_objects.get(key)
|
|
471
|
+
if obj is None:
|
|
472
|
+
continue
|
|
473
|
+
if obj.pin_count > 0:
|
|
474
|
+
logger.debug(
|
|
475
|
+
"Skipping eviction of pinned key %s (pin_count=%d)",
|
|
476
|
+
key,
|
|
477
|
+
obj.pin_count,
|
|
478
|
+
)
|
|
479
|
+
continue
|
|
480
|
+
self._total_bytes -= obj.size
|
|
481
|
+
del self._memory_objects[key]
|
|
482
|
+
to_delete.append((key, self.nixl_agent.get_file_path_for_key(key)))
|
|
483
|
+
# Filesystem I/O outside the lock to avoid blocking concurrent
|
|
484
|
+
# store/lookup/load operations.
|
|
485
|
+
deleted_keys: list[ObjectKey] = []
|
|
486
|
+
for key, file_path in to_delete:
|
|
487
|
+
self.nixl_agent.dynamic_delete_file(file_path)
|
|
488
|
+
deleted_keys.append(key)
|
|
489
|
+
if deleted_keys:
|
|
490
|
+
self._notify_keys_deleted(deleted_keys)
|
|
491
|
+
|
|
492
|
+
def get_usage(self) -> tuple[float, float]:
|
|
493
|
+
"""Return (current_usage, usage_after_ongoing_eviction) in [0, 1]."""
|
|
494
|
+
with self._lock:
|
|
495
|
+
usage = self._total_bytes / self._max_capacity_bytes
|
|
496
|
+
return (usage, usage)
|
|
497
|
+
|
|
498
|
+
#####################
|
|
499
|
+
# Status Interface
|
|
500
|
+
#####################
|
|
501
|
+
|
|
502
|
+
def report_status(self) -> dict:
|
|
503
|
+
with self._lock:
|
|
504
|
+
stored_object_count = len(self._memory_objects)
|
|
505
|
+
pinned_object_count = sum(
|
|
506
|
+
1 for obj in self._memory_objects.values() if obj.pin_count > 0
|
|
507
|
+
)
|
|
508
|
+
return {
|
|
509
|
+
"is_healthy": self._loop_thread.is_alive(),
|
|
510
|
+
"type": "DynamicNixlStoreL2Adapter",
|
|
511
|
+
"backend": self._config.backend,
|
|
512
|
+
"stored_object_count": stored_object_count,
|
|
513
|
+
"pinned_object_count": pinned_object_count,
|
|
514
|
+
"event_loop_alive": self._loop_thread.is_alive(),
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
#####################
|
|
518
|
+
# Cleanup Interface
|
|
519
|
+
#####################
|
|
520
|
+
|
|
521
|
+
def close(self):
|
|
522
|
+
# Stop the event loop and wait for all in-flight tasks to finish
|
|
523
|
+
async def _stop_tasks():
|
|
524
|
+
tasks = [
|
|
525
|
+
t
|
|
526
|
+
for t in asyncio.all_tasks(self._loop)
|
|
527
|
+
if t is not asyncio.current_task()
|
|
528
|
+
]
|
|
529
|
+
for task in tasks:
|
|
530
|
+
task.cancel()
|
|
531
|
+
if tasks:
|
|
532
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
533
|
+
|
|
534
|
+
if self._loop.is_running():
|
|
535
|
+
future = asyncio.run_coroutine_threadsafe(_stop_tasks(), self._loop)
|
|
536
|
+
future.result(timeout=5)
|
|
537
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
538
|
+
|
|
539
|
+
self._loop_thread.join()
|
|
540
|
+
self._loop.close()
|
|
541
|
+
|
|
542
|
+
# If persist is enabled, keep data files on disk; otherwise clean up.
|
|
543
|
+
if self._persist_enabled:
|
|
544
|
+
logger.info("persist_enabled=True, keeping data files on disk")
|
|
545
|
+
else:
|
|
546
|
+
logger.info("persist_enabled=False, deleting all data files")
|
|
547
|
+
with self._lock:
|
|
548
|
+
for key in list(self._memory_objects.keys()):
|
|
549
|
+
file_path = self.nixl_agent.get_file_path_for_key(key)
|
|
550
|
+
self.nixl_agent.dynamic_delete_file(file_path)
|
|
551
|
+
|
|
552
|
+
# Best-effort cleanup of orphaned temp files from crashed stores.
|
|
553
|
+
self.nixl_agent.cleanup_temp_files()
|
|
554
|
+
|
|
555
|
+
self.nixl_agent.close()
|
|
556
|
+
|
|
557
|
+
os.close(self._store_efd)
|
|
558
|
+
os.close(self._lookup_efd)
|
|
559
|
+
os.close(self._load_efd)
|
|
560
|
+
|
|
561
|
+
##################
|
|
562
|
+
# Helper functions
|
|
563
|
+
##################
|
|
564
|
+
|
|
565
|
+
def _run_event_loop(self) -> None:
|
|
566
|
+
asyncio.set_event_loop(self._loop)
|
|
567
|
+
self._loop.run_forever()
|
|
568
|
+
|
|
569
|
+
def _get_next_task_id(self) -> L2TaskId:
|
|
570
|
+
task_id = self._next_task_id
|
|
571
|
+
self._next_task_id += 1
|
|
572
|
+
return task_id
|
|
573
|
+
|
|
574
|
+
def _signal_store_event(self) -> None:
|
|
575
|
+
os.eventfd_write(self._store_efd, 1)
|
|
576
|
+
|
|
577
|
+
def _signal_lookup_event(self) -> None:
|
|
578
|
+
os.eventfd_write(self._lookup_efd, 1)
|
|
579
|
+
|
|
580
|
+
def _signal_load_event(self) -> None:
|
|
581
|
+
os.eventfd_write(self._load_efd, 1)
|
|
582
|
+
|
|
583
|
+
async def _execute_store_in_the_loop(
|
|
584
|
+
self,
|
|
585
|
+
keys: list[ObjectKey],
|
|
586
|
+
objects: list[MemoryObj],
|
|
587
|
+
task_id: L2TaskId,
|
|
588
|
+
) -> None:
|
|
589
|
+
"""Store each key-object pair to its own file via dynamic DMA write."""
|
|
590
|
+
success = True
|
|
591
|
+
stored_keys: list[ObjectKey] = []
|
|
592
|
+
try:
|
|
593
|
+
for key, obj in zip(keys, objects, strict=False):
|
|
594
|
+
mem_addr = obj.meta.address
|
|
595
|
+
mem_size = obj.meta.phy_size
|
|
596
|
+
|
|
597
|
+
# Reserve the key and capacity under the lock *before*
|
|
598
|
+
# the DMA write so that concurrent coroutines (other
|
|
599
|
+
# stores, secondary lookups) see the reservation.
|
|
600
|
+
with self._lock:
|
|
601
|
+
if key in self._memory_objects or key in self._inflight_stores:
|
|
602
|
+
continue
|
|
603
|
+
if self._total_bytes + mem_size > self._max_capacity_bytes:
|
|
604
|
+
logger.warning(
|
|
605
|
+
"Storage capacity exceeded, skipping store for key %s",
|
|
606
|
+
key,
|
|
607
|
+
)
|
|
608
|
+
break
|
|
609
|
+
self._inflight_stores.add(key)
|
|
610
|
+
self._total_bytes += mem_size
|
|
611
|
+
|
|
612
|
+
try:
|
|
613
|
+
mem_indices = self.nixl_agent.get_memory_indices(mem_addr, mem_size)
|
|
614
|
+
file_path = self.nixl_agent.get_file_path_for_key(key)
|
|
615
|
+
|
|
616
|
+
await self.nixl_agent.dynamic_store_file(
|
|
617
|
+
mem_indices, file_path, self.nixl_agent.l1_align_bytes
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
store_obj = NixlStoreObj(
|
|
621
|
+
page_indices=[], # not used in dynamic mode
|
|
622
|
+
size=mem_size,
|
|
623
|
+
layout=MemoryLayoutDesc(
|
|
624
|
+
[obj.meta.shape],
|
|
625
|
+
[obj.meta.dtype],
|
|
626
|
+
),
|
|
627
|
+
pin_count=1,
|
|
628
|
+
)
|
|
629
|
+
with self._lock:
|
|
630
|
+
self._inflight_stores.discard(key)
|
|
631
|
+
self._memory_objects[key] = store_obj
|
|
632
|
+
store_obj.decrease_pin_count()
|
|
633
|
+
stored_keys.append(key)
|
|
634
|
+
except Exception:
|
|
635
|
+
# Un-reserve on failure so capacity accounting
|
|
636
|
+
# stays correct.
|
|
637
|
+
with self._lock:
|
|
638
|
+
self._inflight_stores.discard(key)
|
|
639
|
+
self._total_bytes -= mem_size
|
|
640
|
+
raise
|
|
641
|
+
|
|
642
|
+
except Exception:
|
|
643
|
+
logger.exception("Dynamic NIXL store task %d failed", task_id)
|
|
644
|
+
success = False
|
|
645
|
+
|
|
646
|
+
if stored_keys:
|
|
647
|
+
self._notify_keys_stored(stored_keys)
|
|
648
|
+
|
|
649
|
+
with self._lock:
|
|
650
|
+
self._completed_store_tasks[task_id] = success
|
|
651
|
+
self._signal_store_event()
|
|
652
|
+
|
|
653
|
+
def _execute_lookup_in_the_loop(
|
|
654
|
+
self, keys: list[ObjectKey], task_id: L2TaskId
|
|
655
|
+
) -> None:
|
|
656
|
+
"""Look up keys and pin found objects.
|
|
657
|
+
|
|
658
|
+
Also checks secondary storage (disk) for keys not in the
|
|
659
|
+
in-memory index and lazily populates ``_memory_objects`` for any
|
|
660
|
+
data files found on disk.
|
|
661
|
+
"""
|
|
662
|
+
bitmap = Bitmap(len(keys))
|
|
663
|
+
with self._lock:
|
|
664
|
+
for i, key in enumerate(keys):
|
|
665
|
+
obj = self._memory_objects.get(key)
|
|
666
|
+
if obj is None:
|
|
667
|
+
obj = self._secondary_lookup_locked(key)
|
|
668
|
+
if obj is None:
|
|
669
|
+
continue
|
|
670
|
+
bitmap.set(i)
|
|
671
|
+
obj.increase_pin_count()
|
|
672
|
+
self._completed_lookup_tasks[task_id] = bitmap
|
|
673
|
+
self._signal_lookup_event()
|
|
674
|
+
|
|
675
|
+
def _secondary_lookup_locked(self, key: ObjectKey) -> NixlStoreObj | None:
|
|
676
|
+
"""Check if a data file for ``key`` exists on disk; if so, populate
|
|
677
|
+
``_memory_objects`` and return the entry. Caller must hold ``_lock``.
|
|
678
|
+
|
|
679
|
+
The file size is read via ``os.stat``. Layout is left as ``None`` and
|
|
680
|
+
will be supplied by the caller's MemoryObj at load time.
|
|
681
|
+
"""
|
|
682
|
+
# Skip keys with an in-flight store to avoid double-counting
|
|
683
|
+
# in _total_bytes.
|
|
684
|
+
if key in self._inflight_stores:
|
|
685
|
+
return None
|
|
686
|
+
file_path = self.nixl_agent.get_file_path_for_key(key)
|
|
687
|
+
try:
|
|
688
|
+
obj_size = os.stat(file_path).st_size
|
|
689
|
+
except FileNotFoundError:
|
|
690
|
+
return None
|
|
691
|
+
|
|
692
|
+
# Enforce capacity when populating lazily too.
|
|
693
|
+
if self._total_bytes + obj_size > self._max_capacity_bytes:
|
|
694
|
+
logger.debug(
|
|
695
|
+
"Secondary lookup hit for %s but capacity exceeded, skipping",
|
|
696
|
+
key,
|
|
697
|
+
)
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
obj = NixlStoreObj(
|
|
701
|
+
page_indices=[], # not used in dynamic mode
|
|
702
|
+
size=obj_size,
|
|
703
|
+
layout=None,
|
|
704
|
+
)
|
|
705
|
+
self._memory_objects[key] = obj
|
|
706
|
+
self._total_bytes += obj_size
|
|
707
|
+
return obj
|
|
708
|
+
|
|
709
|
+
async def _execute_load_in_loop(
|
|
710
|
+
self,
|
|
711
|
+
keys: list[ObjectKey],
|
|
712
|
+
objects: list[MemoryObj],
|
|
713
|
+
task_id: L2TaskId,
|
|
714
|
+
) -> None:
|
|
715
|
+
"""Load each found key from its file via dynamic DMA read."""
|
|
716
|
+
bitmap = Bitmap(len(keys))
|
|
717
|
+
accessed_keys: list[ObjectKey] = []
|
|
718
|
+
try:
|
|
719
|
+
for i, key in enumerate(keys):
|
|
720
|
+
with self._lock:
|
|
721
|
+
storage_obj = self._memory_objects.get(key)
|
|
722
|
+
if storage_obj is None:
|
|
723
|
+
continue
|
|
724
|
+
|
|
725
|
+
mem_addr = objects[i].meta.address
|
|
726
|
+
mem_size = objects[i].meta.phy_size
|
|
727
|
+
mem_indices = self.nixl_agent.get_memory_indices(mem_addr, mem_size)
|
|
728
|
+
file_path = self.nixl_agent.get_file_path_for_key(key)
|
|
729
|
+
|
|
730
|
+
await self.nixl_agent.dynamic_load_file(
|
|
731
|
+
mem_indices, file_path, self.nixl_agent.l1_align_bytes
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
bitmap.set(i)
|
|
735
|
+
accessed_keys.append(key)
|
|
736
|
+
|
|
737
|
+
except Exception:
|
|
738
|
+
logger.exception("Dynamic NIXL load task %d failed", task_id)
|
|
739
|
+
|
|
740
|
+
if accessed_keys:
|
|
741
|
+
self._notify_keys_accessed(accessed_keys)
|
|
742
|
+
with self._lock:
|
|
743
|
+
self._completed_load_tasks[task_id] = bitmap
|
|
744
|
+
self._signal_load_event()
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
# ---------------------------------------------------------------------
|
|
748
|
+
# Config and self-registration
|
|
749
|
+
# ---------------------------------------------------------------------
|
|
750
|
+
|
|
751
|
+
# TODO(Jiayi): OBJ backend is not supported in the dynamic adapter yet.
|
|
752
|
+
# Only file-based backends are supported.
|
|
753
|
+
_VALID_DYNAMIC_BACKENDS = ("GDS", "GDS_MT", "POSIX", "HF3FS")
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
class DynamicNixlStoreL2AdapterConfig(L2AdapterConfigBase):
|
|
757
|
+
"""Config for the dynamic-file Nixl L2 adapter.
|
|
758
|
+
|
|
759
|
+
Fields:
|
|
760
|
+
- backend: Nixl storage backend (GDS, GDS_MT, POSIX, HF3FS).
|
|
761
|
+
- backend_params: Backend-specific parameters as a dict of string
|
|
762
|
+
key-value pairs. Must include ``file_path`` and ``use_direct_io``.
|
|
763
|
+
"""
|
|
764
|
+
|
|
765
|
+
def __init__(
|
|
766
|
+
self,
|
|
767
|
+
backend: str,
|
|
768
|
+
backend_params: dict[str, str],
|
|
769
|
+
):
|
|
770
|
+
if backend not in _VALID_DYNAMIC_BACKENDS:
|
|
771
|
+
raise ValueError(
|
|
772
|
+
"backend must be one of %s, got %r" % (_VALID_DYNAMIC_BACKENDS, backend)
|
|
773
|
+
)
|
|
774
|
+
if "file_path" not in backend_params:
|
|
775
|
+
raise ValueError(
|
|
776
|
+
"backend_params must include 'file_path' for backend %r" % backend
|
|
777
|
+
)
|
|
778
|
+
if "use_direct_io" not in backend_params:
|
|
779
|
+
raise ValueError(
|
|
780
|
+
"backend_params must include 'use_direct_io' for backend %r" % backend
|
|
781
|
+
)
|
|
782
|
+
self.backend = backend
|
|
783
|
+
self.backend_params = backend_params
|
|
784
|
+
|
|
785
|
+
@classmethod
|
|
786
|
+
def from_dict(cls, d: dict) -> DynamicNixlStoreL2AdapterConfig:
|
|
787
|
+
backend = d.get("backend")
|
|
788
|
+
if backend not in _VALID_DYNAMIC_BACKENDS:
|
|
789
|
+
raise ValueError(
|
|
790
|
+
"backend must be one of %s, got %r" % (_VALID_DYNAMIC_BACKENDS, backend)
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
backend_params = d.get("backend_params", {})
|
|
794
|
+
if not isinstance(backend_params, dict):
|
|
795
|
+
raise ValueError("backend_params must be a dict of string key-value pairs")
|
|
796
|
+
|
|
797
|
+
return cls(backend=backend, backend_params=backend_params)
|
|
798
|
+
|
|
799
|
+
@classmethod
|
|
800
|
+
def help(cls) -> str:
|
|
801
|
+
return (
|
|
802
|
+
"Dynamic Nixl store L2 adapter config fields:\n"
|
|
803
|
+
"- backend (str): Nixl storage backend, "
|
|
804
|
+
"one of %s (required)\n"
|
|
805
|
+
"- backend_params (dict): backend-specific "
|
|
806
|
+
"string key-value pairs. Must include "
|
|
807
|
+
"'file_path' and 'use_direct_io'.\n"
|
|
808
|
+
"- persist_enabled (bool): if True, keep data files on disk "
|
|
809
|
+
"at shutdown (optional, default True)\n"
|
|
810
|
+
"Lookup always checks secondary storage (disk) on miss."
|
|
811
|
+
% (_VALID_DYNAMIC_BACKENDS,)
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
# Self-register config type and adapter factory
|
|
816
|
+
register_l2_adapter_type("nixl_store_dynamic", DynamicNixlStoreL2AdapterConfig)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _create_dynamic_nixl_store_adapter(
|
|
820
|
+
config: L2AdapterConfigBase,
|
|
821
|
+
l1_memory_desc: Optional[L1MemoryDesc] = None,
|
|
822
|
+
) -> L2AdapterInterface:
|
|
823
|
+
"""Create a DynamicNixlStoreL2Adapter from config."""
|
|
824
|
+
if l1_memory_desc is None:
|
|
825
|
+
raise ValueError(
|
|
826
|
+
"l1_memory_desc is required to create a DynamicNixlStoreL2Adapter."
|
|
827
|
+
)
|
|
828
|
+
return DynamicNixlStoreL2Adapter(config, l1_memory_desc) # type: ignore[arg-type]
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
register_l2_adapter_factory("nixl_store_dynamic", _create_dynamic_nixl_store_adapter)
|