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,983 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
# Future
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
# Standard
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
import threading
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
# Third Party
|
|
15
|
+
from nixl._api import nixl_agent as NixlAgent
|
|
16
|
+
from nixl._api import nixl_agent_config as NixlAgentConfig
|
|
17
|
+
from nixl._api import nixl_prepped_dlist_handle as NixlDlistHandle
|
|
18
|
+
from nixl._api import nixl_xfer_handle as NixlXferHandle
|
|
19
|
+
from nixl._api import (
|
|
20
|
+
nixlBind,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# First Party
|
|
24
|
+
from lmcache.logging import init_logger
|
|
25
|
+
from lmcache.native_storage_ops import Bitmap
|
|
26
|
+
from lmcache.v1.distributed.api import MemoryLayoutDesc, ObjectKey
|
|
27
|
+
from lmcache.v1.distributed.internal_api import L1MemoryDesc
|
|
28
|
+
from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
|
|
29
|
+
from lmcache.v1.distributed.l2_adapters.config import (
|
|
30
|
+
L2AdapterConfigBase,
|
|
31
|
+
register_l2_adapter_type,
|
|
32
|
+
)
|
|
33
|
+
from lmcache.v1.distributed.l2_adapters.factory import (
|
|
34
|
+
register_l2_adapter_factory,
|
|
35
|
+
)
|
|
36
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
37
|
+
|
|
38
|
+
logger = init_logger(__name__)
|
|
39
|
+
|
|
40
|
+
# Main class
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class NixlStoreObj:
|
|
45
|
+
"""
|
|
46
|
+
The object stored in Nixl L2 cache.
|
|
47
|
+
Can be used for both file and object.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
page_indices: list[int]
|
|
51
|
+
|
|
52
|
+
size: int # in bytes
|
|
53
|
+
|
|
54
|
+
layout: Optional[MemoryLayoutDesc] = None
|
|
55
|
+
|
|
56
|
+
pin_count: int = 0
|
|
57
|
+
_lock: threading.Lock = field(
|
|
58
|
+
default_factory=threading.Lock, repr=False, compare=False
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def increase_pin_count(self):
|
|
62
|
+
with self._lock:
|
|
63
|
+
self.pin_count += 1
|
|
64
|
+
|
|
65
|
+
def decrease_pin_count(self):
|
|
66
|
+
with self._lock:
|
|
67
|
+
if self.pin_count > 0:
|
|
68
|
+
self.pin_count -= 1
|
|
69
|
+
else:
|
|
70
|
+
logger.warning(
|
|
71
|
+
"Trying to decrease pin count of object at page indices %s below 0",
|
|
72
|
+
self.page_indices,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class NixlObjPool:
|
|
77
|
+
"""Thread-safe pool of integer indices representing pre-allocated storage slots."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, num_total_objs: int):
|
|
80
|
+
"""
|
|
81
|
+
Args:
|
|
82
|
+
num_total_objs: Total number of storage slots to manage.
|
|
83
|
+
"""
|
|
84
|
+
self.indices = list(range(num_total_objs))
|
|
85
|
+
self._total = num_total_objs
|
|
86
|
+
self._lock = threading.Lock()
|
|
87
|
+
|
|
88
|
+
def batched_allocate(self, num_objs: int) -> list[int]:
|
|
89
|
+
"""
|
|
90
|
+
Allocate a batch of storage slot indices.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
num_objs: Number of indices to allocate.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
list[int]: The allocated indices.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
RuntimeError: If fewer than ``num_objs`` slots remain in the pool.
|
|
100
|
+
"""
|
|
101
|
+
with self._lock:
|
|
102
|
+
if num_objs > len(self.indices):
|
|
103
|
+
logger.debug("NixlObjPool allocation failure.")
|
|
104
|
+
return []
|
|
105
|
+
allocated = self.indices[:num_objs]
|
|
106
|
+
self.indices = self.indices[num_objs:]
|
|
107
|
+
return allocated
|
|
108
|
+
|
|
109
|
+
def batched_free(self, obj_indices: list[int]) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Return a batch of storage slot indices back to the pool.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
obj_indices: Indices previously obtained from ``batched_allocate``.
|
|
115
|
+
"""
|
|
116
|
+
with self._lock:
|
|
117
|
+
self.indices.extend(obj_indices)
|
|
118
|
+
|
|
119
|
+
def get_usage(self) -> tuple[float, float]:
|
|
120
|
+
"""
|
|
121
|
+
Return (current_usage, usage_after_ongoing_eviction) in [0, 1].
|
|
122
|
+
|
|
123
|
+
Both values are identical because slot frees are synchronous.
|
|
124
|
+
"""
|
|
125
|
+
with self._lock:
|
|
126
|
+
if self._total == 0:
|
|
127
|
+
return (0.0, 0.0)
|
|
128
|
+
usage = (self._total - len(self.indices)) / self._total
|
|
129
|
+
return (usage, usage)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class NixlStorageAgent:
|
|
133
|
+
agent_name: str
|
|
134
|
+
nixl_agent: NixlAgent
|
|
135
|
+
mem_reg_descs: nixlBind.nixlRegDList
|
|
136
|
+
mem_xfer_handler: NixlDlistHandle
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
device: str,
|
|
141
|
+
backend: str,
|
|
142
|
+
backend_params: dict[str, str],
|
|
143
|
+
pool_size: int,
|
|
144
|
+
l1_memory_desc: L1MemoryDesc,
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
Initialize the NixlStorageAgent.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
device: Device type of the L1 memory buffer (e.g. "cpu", "cuda").
|
|
151
|
+
backend: Nixl storage backend to use. One of: GDS, GDS_MT, POSIX,
|
|
152
|
+
HF3FS (file-based) or OBJ (object-based).
|
|
153
|
+
backend_params: Backend-specific parameters. File-based backends
|
|
154
|
+
require "file_path" and "use_direct_io" keys.
|
|
155
|
+
pool_size: Number of storage descriptor slots to pre-allocate.
|
|
156
|
+
l1_memory_desc: Descriptor of the L1 memory buffer to register with Nixl
|
|
157
|
+
for data transfers.
|
|
158
|
+
"""
|
|
159
|
+
self.backend = backend
|
|
160
|
+
self.pool_size = pool_size
|
|
161
|
+
self.device = device
|
|
162
|
+
self.backend_params = backend_params
|
|
163
|
+
|
|
164
|
+
self.l1_align_bytes = l1_memory_desc.align_bytes
|
|
165
|
+
|
|
166
|
+
self.agent_name = "NixlAgent_" + str(uuid.uuid4())
|
|
167
|
+
nixl_conf = NixlAgentConfig(backends=[])
|
|
168
|
+
self.nixl_agent = NixlAgent(self.agent_name, nixl_conf)
|
|
169
|
+
self.nixl_agent.create_backend(backend, backend_params)
|
|
170
|
+
|
|
171
|
+
self.init_mem_handlers(
|
|
172
|
+
self.device,
|
|
173
|
+
l1_memory_desc.ptr,
|
|
174
|
+
l1_memory_desc.size,
|
|
175
|
+
l1_memory_desc.align_bytes,
|
|
176
|
+
device_id=0, # 0 indicates cpu
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if self.backend in ["GDS", "GDS_MT", "POSIX", "HF3FS"]:
|
|
180
|
+
file_size = int(
|
|
181
|
+
self.backend_params.get("file_size", l1_memory_desc.align_bytes)
|
|
182
|
+
)
|
|
183
|
+
pages_per_file = file_size // l1_memory_desc.align_bytes
|
|
184
|
+
self.pool = NixlObjPool(num_total_objs=self.pool_size * pages_per_file)
|
|
185
|
+
self.init_storage_handlers_file(
|
|
186
|
+
num_files=self.pool_size,
|
|
187
|
+
page_size=l1_memory_desc.align_bytes,
|
|
188
|
+
file_size=file_size,
|
|
189
|
+
file_path=self.backend_params["file_path"],
|
|
190
|
+
# TODO(Jiayi): Need to make argument parsing more elegant
|
|
191
|
+
use_direct_io=str(self.backend_params["use_direct_io"]).lower()
|
|
192
|
+
== "true",
|
|
193
|
+
)
|
|
194
|
+
elif self.backend in ["OBJ"]:
|
|
195
|
+
self.pool = NixlObjPool(num_total_objs=self.pool_size)
|
|
196
|
+
self.init_storage_handlers_object(
|
|
197
|
+
page_size=l1_memory_desc.align_bytes,
|
|
198
|
+
num_pages=self.pool_size,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
raise TypeError(f"Unsupported backend type: {self.backend}")
|
|
202
|
+
|
|
203
|
+
def init_mem_handlers(self, device, buffer_ptr, buffer_size, page_size, device_id):
|
|
204
|
+
"""
|
|
205
|
+
Initialize memory handlers for the given device and buffer.
|
|
206
|
+
"""
|
|
207
|
+
reg_list = [(buffer_ptr, buffer_size, device_id, "")]
|
|
208
|
+
xfer_desc = [
|
|
209
|
+
(base_addr, page_size, device_id)
|
|
210
|
+
for base_addr in range(buffer_ptr, buffer_ptr + buffer_size, page_size)
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
if device == "cpu":
|
|
214
|
+
mem_type = "DRAM"
|
|
215
|
+
else:
|
|
216
|
+
mem_type = "VRAM"
|
|
217
|
+
|
|
218
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type=mem_type)
|
|
219
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type=mem_type)
|
|
220
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
221
|
+
"", xfer_descs, mem_type=mem_type
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.mem_reg_descs = reg_descs
|
|
225
|
+
self.mem_xfer_handler = xfer_handler
|
|
226
|
+
|
|
227
|
+
def init_storage_handlers_file(
|
|
228
|
+
self,
|
|
229
|
+
num_files: int,
|
|
230
|
+
page_size: int,
|
|
231
|
+
file_size: int,
|
|
232
|
+
file_path: str,
|
|
233
|
+
use_direct_io: bool,
|
|
234
|
+
):
|
|
235
|
+
"""Initialize storage handlers for file-based backends.
|
|
236
|
+
|
|
237
|
+
Each file holds ``file_size // page_size`` pages at successive offsets.
|
|
238
|
+
``file_size`` must be a multiple of ``page_size``.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
num_files: Number of storage files to create.
|
|
242
|
+
page_size: Granularity of L1 memory pages (transfer unit size).
|
|
243
|
+
file_size: Size in bytes of each storage file. Must be a multiple
|
|
244
|
+
of ``page_size``.
|
|
245
|
+
file_path: Directory where storage files are created.
|
|
246
|
+
use_direct_io: Whether to open files with O_DIRECT.
|
|
247
|
+
"""
|
|
248
|
+
if file_size % page_size != 0:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"file_size ({file_size}) must be a multiple of page_size ({page_size})"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
pages_per_file = file_size // page_size
|
|
254
|
+
num_pages = num_files * pages_per_file
|
|
255
|
+
|
|
256
|
+
# Create file descriptors for Nixl to register
|
|
257
|
+
fds: list[int] = []
|
|
258
|
+
flags = os.O_CREAT | os.O_RDWR
|
|
259
|
+
if use_direct_io:
|
|
260
|
+
if hasattr(os, "O_DIRECT"):
|
|
261
|
+
flags |= os.O_DIRECT
|
|
262
|
+
else:
|
|
263
|
+
logger.warning(
|
|
264
|
+
"use_direct_io is True, but O_DIRECT is not available on "
|
|
265
|
+
"this system. Falling back to buffered I/O."
|
|
266
|
+
)
|
|
267
|
+
for i in range(num_files):
|
|
268
|
+
filename = f"obj_{i}_{uuid.uuid4().hex[0:4]}.bin"
|
|
269
|
+
tmp_path = os.path.join(file_path, filename)
|
|
270
|
+
fd = os.open(tmp_path, flags)
|
|
271
|
+
fds.append(fd)
|
|
272
|
+
|
|
273
|
+
# Register each file covering the full file_size.
|
|
274
|
+
# Build one xfer_desc entry per page slot (page index i maps to
|
|
275
|
+
# offset (i % pages_per_file) * page_size inside fd[i // pages_per_file]).
|
|
276
|
+
reg_list = []
|
|
277
|
+
xfer_desc = []
|
|
278
|
+
for fd in fds:
|
|
279
|
+
reg_list.append((0, file_size, fd, ""))
|
|
280
|
+
for page_idx in range(num_pages):
|
|
281
|
+
fd = fds[page_idx // pages_per_file]
|
|
282
|
+
offset = (page_idx % pages_per_file) * page_size
|
|
283
|
+
xfer_desc.append((offset, page_size, fd))
|
|
284
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type="FILE")
|
|
285
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type="FILE")
|
|
286
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
287
|
+
self.agent_name, xfer_descs, mem_type="FILE"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
self.storage_fds = fds
|
|
291
|
+
self.storage_reg_descs = reg_descs
|
|
292
|
+
self.storage_xfer_descs = xfer_descs
|
|
293
|
+
self.storage_xfer_handler = xfer_handler
|
|
294
|
+
|
|
295
|
+
def init_storage_handlers_object(
|
|
296
|
+
self,
|
|
297
|
+
page_size: int,
|
|
298
|
+
num_pages: int,
|
|
299
|
+
):
|
|
300
|
+
"""Initialize storage handlers for object-based backends."""
|
|
301
|
+
|
|
302
|
+
# Create object keys for Nixl to register
|
|
303
|
+
keys = []
|
|
304
|
+
|
|
305
|
+
for i in range(num_pages):
|
|
306
|
+
key = f"obj_{i}_{uuid.uuid4().hex[0:4]}"
|
|
307
|
+
keys.append(key)
|
|
308
|
+
|
|
309
|
+
# Register and prepare xfer handler
|
|
310
|
+
reg_list = []
|
|
311
|
+
xfer_desc = []
|
|
312
|
+
for i, key in enumerate(keys):
|
|
313
|
+
reg_list.append((0, page_size, i, key))
|
|
314
|
+
xfer_desc.append((0, page_size, i))
|
|
315
|
+
reg_descs = self.nixl_agent.register_memory(reg_list, mem_type="OBJ")
|
|
316
|
+
xfer_descs = self.nixl_agent.get_xfer_descs(xfer_desc, mem_type="OBJ")
|
|
317
|
+
xfer_handler = self.nixl_agent.prep_xfer_dlist(
|
|
318
|
+
self.agent_name, xfer_descs, mem_type="OBJ"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
self.storage_reg_descs = reg_descs
|
|
322
|
+
self.storage_xfer_descs = xfer_descs
|
|
323
|
+
self.storage_xfer_handler = xfer_handler
|
|
324
|
+
|
|
325
|
+
def get_mem_to_storage_handle(self, mem_indices, storage_indices) -> NixlXferHandle:
|
|
326
|
+
"""Get a Nixl transfer handle for transferring data from memory to storage."""
|
|
327
|
+
|
|
328
|
+
return self.nixl_agent.make_prepped_xfer(
|
|
329
|
+
"WRITE",
|
|
330
|
+
self.mem_xfer_handler,
|
|
331
|
+
mem_indices,
|
|
332
|
+
self.storage_xfer_handler,
|
|
333
|
+
storage_indices,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def get_storage_to_mem_handle(self, mem_indices, storage_indices) -> NixlXferHandle:
|
|
337
|
+
"""Get a Nixl transfer handle for transferring data from storage to memory."""
|
|
338
|
+
return self.nixl_agent.make_prepped_xfer(
|
|
339
|
+
"READ",
|
|
340
|
+
self.mem_xfer_handler,
|
|
341
|
+
mem_indices,
|
|
342
|
+
self.storage_xfer_handler,
|
|
343
|
+
storage_indices,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def post_blocking(self, handle: NixlXferHandle):
|
|
347
|
+
"""Post a Nixl transfer handle and block until the transfer is done."""
|
|
348
|
+
|
|
349
|
+
state = self.nixl_agent.transfer(handle)
|
|
350
|
+
|
|
351
|
+
while state != "DONE" and state != "ERR":
|
|
352
|
+
try:
|
|
353
|
+
state = self.nixl_agent.check_xfer_state(handle)
|
|
354
|
+
except nixlBind.nixlBackendError:
|
|
355
|
+
raise
|
|
356
|
+
|
|
357
|
+
if state == "ERR":
|
|
358
|
+
raise RuntimeError("NIXL transfer failed")
|
|
359
|
+
|
|
360
|
+
async def post_non_blocking(self, handle: NixlXferHandle):
|
|
361
|
+
"""Post a Nixl transfer handle and await until the transfer is done."""
|
|
362
|
+
|
|
363
|
+
state = self.nixl_agent.transfer(handle)
|
|
364
|
+
|
|
365
|
+
while state != "DONE" and state != "ERR":
|
|
366
|
+
try:
|
|
367
|
+
state = self.nixl_agent.check_xfer_state(handle)
|
|
368
|
+
except nixlBind.nixlBackendError:
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
# TODO(Jiayi): Tune this for better perf
|
|
372
|
+
await asyncio.sleep(0.01)
|
|
373
|
+
|
|
374
|
+
if state == "ERR":
|
|
375
|
+
raise RuntimeError("NIXL transfer failed")
|
|
376
|
+
|
|
377
|
+
def get_storage_indices(self, num_objs: int) -> list[int]:
|
|
378
|
+
# TODO(Jiayi): Support eviction
|
|
379
|
+
return self.pool.batched_allocate(num_objs)
|
|
380
|
+
|
|
381
|
+
def get_memory_indices(self, raw_addr: int, mem_size: int) -> list[int]:
|
|
382
|
+
"""Get memory indices for the given raw address and size."""
|
|
383
|
+
# TODO(Jiayi): Now we assume the memory is contiguous and page-aligned. We may
|
|
384
|
+
# want to support more flexible memory layout in the future.
|
|
385
|
+
if raw_addr % self.l1_align_bytes != 0:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f"Raw address {raw_addr} is not aligned to "
|
|
388
|
+
f"page size {self.l1_align_bytes}"
|
|
389
|
+
)
|
|
390
|
+
if mem_size % self.l1_align_bytes != 0:
|
|
391
|
+
raise ValueError(
|
|
392
|
+
f"Memory size {mem_size} is not a multiple of "
|
|
393
|
+
f"page size {self.l1_align_bytes}"
|
|
394
|
+
)
|
|
395
|
+
num_pages = mem_size // self.l1_align_bytes
|
|
396
|
+
return [(raw_addr // self.l1_align_bytes + i) for i in range(num_pages)]
|
|
397
|
+
|
|
398
|
+
def release_handle(self, handle):
|
|
399
|
+
self.nixl_agent.release_xfer_handle(handle)
|
|
400
|
+
|
|
401
|
+
def close(self):
|
|
402
|
+
self.nixl_agent.release_dlist_handle(self.storage_xfer_handler)
|
|
403
|
+
self.nixl_agent.release_dlist_handle(self.mem_xfer_handler)
|
|
404
|
+
self.nixl_agent.deregister_memory(self.storage_reg_descs)
|
|
405
|
+
self.nixl_agent.deregister_memory(self.mem_reg_descs)
|
|
406
|
+
for fd in getattr(self, "storage_fds", []):
|
|
407
|
+
os.close(fd)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class NixlStoreL2Adapter(L2AdapterInterface):
|
|
411
|
+
"""
|
|
412
|
+
A Nixl-based L2 adapter
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
def __init__(self, config: NixlStoreL2AdapterConfig, l1_memory_desc: L1MemoryDesc):
|
|
416
|
+
"""
|
|
417
|
+
Initialize the NixlStoreL2Adapter.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
config: Nixl-specific adapter configuration including backend type,
|
|
421
|
+
backend parameters, and storage pool size.
|
|
422
|
+
l1_memory_desc: Descriptor of the L1 memory buffer to register with the
|
|
423
|
+
Nixl backend for DMA transfers.
|
|
424
|
+
"""
|
|
425
|
+
super().__init__()
|
|
426
|
+
self._config = config
|
|
427
|
+
|
|
428
|
+
self._store_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
429
|
+
self._lookup_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
430
|
+
self._load_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
431
|
+
|
|
432
|
+
# Cache data structures
|
|
433
|
+
self._memory_objects: dict[ObjectKey, NixlStoreObj] = {}
|
|
434
|
+
|
|
435
|
+
# Task ID management
|
|
436
|
+
self._next_task_id: L2TaskId = 0
|
|
437
|
+
self._completed_store_tasks: dict[L2TaskId, bool] = {}
|
|
438
|
+
self._completed_lookup_tasks: dict[L2TaskId, Bitmap] = {}
|
|
439
|
+
self._completed_load_tasks: dict[L2TaskId, Bitmap] = {}
|
|
440
|
+
self._lock = threading.Lock() # lock for all shared state
|
|
441
|
+
|
|
442
|
+
# Asyncio event loop running in a background thread
|
|
443
|
+
self._loop = asyncio.new_event_loop()
|
|
444
|
+
self._loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
|
445
|
+
self._loop_thread.start()
|
|
446
|
+
|
|
447
|
+
# Initialize Nixl agent
|
|
448
|
+
self.nixl_agent = NixlStorageAgent(
|
|
449
|
+
device="cpu",
|
|
450
|
+
backend=config.backend,
|
|
451
|
+
backend_params=config.backend_params,
|
|
452
|
+
pool_size=config.pool_size,
|
|
453
|
+
l1_memory_desc=l1_memory_desc,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# --------------------
|
|
457
|
+
# Event Fd Interface
|
|
458
|
+
# --------------------
|
|
459
|
+
|
|
460
|
+
def get_store_event_fd(self) -> int:
|
|
461
|
+
return self._store_efd
|
|
462
|
+
|
|
463
|
+
def get_lookup_and_lock_event_fd(self) -> int:
|
|
464
|
+
return self._lookup_efd
|
|
465
|
+
|
|
466
|
+
def get_load_event_fd(self) -> int:
|
|
467
|
+
return self._load_efd
|
|
468
|
+
|
|
469
|
+
#####################
|
|
470
|
+
# Store Interface
|
|
471
|
+
#####################
|
|
472
|
+
|
|
473
|
+
def submit_store_task(
|
|
474
|
+
self,
|
|
475
|
+
keys: list[ObjectKey],
|
|
476
|
+
objects: list[MemoryObj],
|
|
477
|
+
) -> L2TaskId:
|
|
478
|
+
"""
|
|
479
|
+
Submit a store task to store a batch of memory objects associated with
|
|
480
|
+
a batch of keys.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
keys (list[ObjectKey]): the list of keys to be stored.
|
|
484
|
+
objects (list[MemoryObj]): the list of memory objects to be stored.
|
|
485
|
+
The length of the objects list should be the same as the length of
|
|
486
|
+
the keys list.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
L2TaskId: the task id of the submitted store task.
|
|
490
|
+
"""
|
|
491
|
+
with self._lock:
|
|
492
|
+
task_id = self._get_next_task_id()
|
|
493
|
+
|
|
494
|
+
asyncio.run_coroutine_threadsafe(
|
|
495
|
+
self._execute_store_in_the_loop(keys, objects, task_id), self._loop
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
return task_id
|
|
499
|
+
|
|
500
|
+
def pop_completed_store_tasks(self) -> dict[L2TaskId, bool]:
|
|
501
|
+
"""
|
|
502
|
+
Pop all the completed store tasks with a flag indicating
|
|
503
|
+
whether the task is successful or not.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
dict[L2TaskId, bool]: a dictionary mapping the task id to a boolean flag
|
|
507
|
+
indicating whether the task is successful or not. True means
|
|
508
|
+
successful, and False means failed.
|
|
509
|
+
"""
|
|
510
|
+
with self._lock:
|
|
511
|
+
completed = self._completed_store_tasks
|
|
512
|
+
self._completed_store_tasks = {}
|
|
513
|
+
return completed
|
|
514
|
+
|
|
515
|
+
#####################
|
|
516
|
+
# Lookup and Lock Interface
|
|
517
|
+
#####################
|
|
518
|
+
|
|
519
|
+
def submit_lookup_and_lock_task(self, keys: list[ObjectKey]) -> L2TaskId:
|
|
520
|
+
with self._lock:
|
|
521
|
+
task_id = self._get_next_task_id()
|
|
522
|
+
|
|
523
|
+
# Schedule the lookup operation in the event loop thread
|
|
524
|
+
self._loop.call_soon_threadsafe(self._execute_lookup_in_the_loop, keys, task_id)
|
|
525
|
+
return task_id
|
|
526
|
+
|
|
527
|
+
def query_lookup_and_lock_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
528
|
+
with self._lock:
|
|
529
|
+
return self._completed_lookup_tasks.pop(task_id, None)
|
|
530
|
+
|
|
531
|
+
def submit_unlock(self, keys: list[ObjectKey]) -> None:
|
|
532
|
+
def _unlock_keys(keys: list[ObjectKey]) -> None:
|
|
533
|
+
"""
|
|
534
|
+
Unlock keys in the event loop thread.
|
|
535
|
+
"""
|
|
536
|
+
for key in keys:
|
|
537
|
+
if (obj := self._memory_objects.get(key)) is not None:
|
|
538
|
+
obj.decrease_pin_count()
|
|
539
|
+
|
|
540
|
+
# Schedule the unlock operation in the event loop thread
|
|
541
|
+
self._loop.call_soon_threadsafe(_unlock_keys, keys)
|
|
542
|
+
|
|
543
|
+
#####################
|
|
544
|
+
# Load Interface
|
|
545
|
+
######################
|
|
546
|
+
|
|
547
|
+
def submit_load_task(
|
|
548
|
+
self,
|
|
549
|
+
keys: list[ObjectKey],
|
|
550
|
+
objects: list[MemoryObj],
|
|
551
|
+
) -> L2TaskId:
|
|
552
|
+
with self._lock:
|
|
553
|
+
task_id = self._get_next_task_id()
|
|
554
|
+
|
|
555
|
+
# Schedule the load operation in the event loop thread
|
|
556
|
+
asyncio.run_coroutine_threadsafe(
|
|
557
|
+
self._execute_load_in_loop(keys, objects, task_id), self._loop
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
return task_id
|
|
561
|
+
|
|
562
|
+
def query_load_result(self, task_id: L2TaskId) -> Bitmap | None:
|
|
563
|
+
with self._lock:
|
|
564
|
+
return self._completed_load_tasks.pop(task_id, None)
|
|
565
|
+
|
|
566
|
+
def close(self):
|
|
567
|
+
# Stop the event loop and wait for the thread to finish
|
|
568
|
+
async def _stop_tasks():
|
|
569
|
+
tasks = [
|
|
570
|
+
t
|
|
571
|
+
for t in asyncio.all_tasks(self._loop)
|
|
572
|
+
if t is not asyncio.current_task()
|
|
573
|
+
]
|
|
574
|
+
for task in tasks:
|
|
575
|
+
task.cancel()
|
|
576
|
+
if tasks:
|
|
577
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
578
|
+
|
|
579
|
+
if self._loop.is_running():
|
|
580
|
+
future = asyncio.run_coroutine_threadsafe(_stop_tasks(), self._loop)
|
|
581
|
+
|
|
582
|
+
future.result(timeout=5) # Wait for tasks to be cancelled, with a timeout
|
|
583
|
+
|
|
584
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
585
|
+
|
|
586
|
+
self._loop_thread.join()
|
|
587
|
+
self._loop.close()
|
|
588
|
+
|
|
589
|
+
os.close(self._store_efd)
|
|
590
|
+
os.close(self._lookup_efd)
|
|
591
|
+
os.close(self._load_efd)
|
|
592
|
+
|
|
593
|
+
#####################
|
|
594
|
+
# Eviction Interface
|
|
595
|
+
#####################
|
|
596
|
+
|
|
597
|
+
def delete(self, keys: list[ObjectKey]) -> None:
|
|
598
|
+
"""
|
|
599
|
+
Delete a batch of objects from Nixl storage, freeing their page slots.
|
|
600
|
+
|
|
601
|
+
Pinned objects (pin_count > 0) are skipped to avoid racing with an
|
|
602
|
+
in-flight load; the eviction controller will retry them on the next
|
|
603
|
+
cycle once they are unpinned.
|
|
604
|
+
"""
|
|
605
|
+
# TODO(Jiayi): Optimize lock usage here
|
|
606
|
+
deleted_keys: list[ObjectKey] = []
|
|
607
|
+
with self._lock:
|
|
608
|
+
for key in keys:
|
|
609
|
+
obj = self._memory_objects.get(key)
|
|
610
|
+
if obj is None:
|
|
611
|
+
continue
|
|
612
|
+
if obj.pin_count > 0:
|
|
613
|
+
logger.debug(
|
|
614
|
+
"Skipping eviction of pinned key %s (pin_count=%d)",
|
|
615
|
+
key,
|
|
616
|
+
obj.pin_count,
|
|
617
|
+
)
|
|
618
|
+
continue
|
|
619
|
+
del self._memory_objects[key]
|
|
620
|
+
self.nixl_agent.pool.batched_free(obj.page_indices)
|
|
621
|
+
deleted_keys.append(key)
|
|
622
|
+
if deleted_keys:
|
|
623
|
+
self._notify_keys_deleted(deleted_keys)
|
|
624
|
+
|
|
625
|
+
def get_usage(self) -> tuple[float, float]:
|
|
626
|
+
"""
|
|
627
|
+
Return (current_usage, usage_after_ongoing_eviction) based on pool slots.
|
|
628
|
+
"""
|
|
629
|
+
return self.nixl_agent.pool.get_usage()
|
|
630
|
+
|
|
631
|
+
#####################
|
|
632
|
+
# Status Interface
|
|
633
|
+
#####################
|
|
634
|
+
|
|
635
|
+
def report_status(self) -> dict:
|
|
636
|
+
"""Return a status dict for the Nixl L2 adapter."""
|
|
637
|
+
# NOTE(Jiayi): This function looks pretty slow.
|
|
638
|
+
with self._lock:
|
|
639
|
+
stored_object_count = len(self._memory_objects)
|
|
640
|
+
pinned_object_count = sum(
|
|
641
|
+
1 for obj in self._memory_objects.values() if obj.pin_count > 0
|
|
642
|
+
)
|
|
643
|
+
pool = self.nixl_agent.pool
|
|
644
|
+
with pool._lock:
|
|
645
|
+
pool_free_slots = len(pool.indices)
|
|
646
|
+
return {
|
|
647
|
+
"is_healthy": self._loop_thread.is_alive(),
|
|
648
|
+
"type": "NixlStoreL2Adapter",
|
|
649
|
+
"backend": self._config.backend,
|
|
650
|
+
"stored_object_count": stored_object_count,
|
|
651
|
+
"pinned_object_count": pinned_object_count,
|
|
652
|
+
"pool_size": self._config.pool_size,
|
|
653
|
+
"pool_free_slots": pool_free_slots,
|
|
654
|
+
"event_loop_alive": self._loop_thread.is_alive(),
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
##################
|
|
658
|
+
# Helper functions
|
|
659
|
+
##################
|
|
660
|
+
|
|
661
|
+
def _run_event_loop(self) -> None:
|
|
662
|
+
"""Run the asyncio event loop in a background thread."""
|
|
663
|
+
asyncio.set_event_loop(self._loop)
|
|
664
|
+
self._loop.run_forever()
|
|
665
|
+
|
|
666
|
+
def _get_next_task_id(self) -> L2TaskId:
|
|
667
|
+
"""Get the next task ID and increment the counter."""
|
|
668
|
+
task_id = self._next_task_id
|
|
669
|
+
self._next_task_id += 1
|
|
670
|
+
return task_id
|
|
671
|
+
|
|
672
|
+
def _evict_if_needed(
|
|
673
|
+
self,
|
|
674
|
+
) -> None:
|
|
675
|
+
"""
|
|
676
|
+
Evict objects from the cache using desired caching policy.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
# TODO(Jiayi): Support eviction
|
|
680
|
+
|
|
681
|
+
pass
|
|
682
|
+
|
|
683
|
+
def _signal_store_event(self) -> None:
|
|
684
|
+
"""Signal the store event fd to notify completion."""
|
|
685
|
+
os.eventfd_write(self._store_efd, 1)
|
|
686
|
+
|
|
687
|
+
async def _execute_store_in_the_loop(
|
|
688
|
+
self,
|
|
689
|
+
keys: list[ObjectKey],
|
|
690
|
+
objects: list[MemoryObj],
|
|
691
|
+
task_id: L2TaskId,
|
|
692
|
+
) -> None:
|
|
693
|
+
"""
|
|
694
|
+
Coroutine that performs a batched store to Nixl storage.
|
|
695
|
+
|
|
696
|
+
For each key-object pair, memory page indices are mapped to storage
|
|
697
|
+
slot indices and a single batched DMA write is issued. On success the
|
|
698
|
+
key-to-storage mapping is recorded in ``_memory_objects``. On transfer
|
|
699
|
+
failure, all allocated storage slots are freed and the task is marked
|
|
700
|
+
as failed.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
keys: Keys identifying each object to store.
|
|
704
|
+
objects: Memory objects whose contents will be written to storage.
|
|
705
|
+
Must be the same length as ``keys``.
|
|
706
|
+
task_id: Identifier used to report completion via
|
|
707
|
+
``_completed_store_tasks``.
|
|
708
|
+
"""
|
|
709
|
+
success = True
|
|
710
|
+
try:
|
|
711
|
+
# Get memory page indices and storage slot indices
|
|
712
|
+
mem_indices_flat = []
|
|
713
|
+
storage_indices_flat = []
|
|
714
|
+
stored_keys = []
|
|
715
|
+
storage_objs = []
|
|
716
|
+
for key, obj in zip(keys, objects, strict=False):
|
|
717
|
+
# Skip if key already exists to avoid leaking pool slots
|
|
718
|
+
with self._lock:
|
|
719
|
+
if key in self._memory_objects:
|
|
720
|
+
continue
|
|
721
|
+
|
|
722
|
+
mem_addr = obj.meta.address
|
|
723
|
+
mem_size = obj.meta.phy_size
|
|
724
|
+
mem_indices = self.nixl_agent.get_memory_indices(mem_addr, mem_size)
|
|
725
|
+
storage_indices = self.nixl_agent.get_storage_indices(
|
|
726
|
+
num_objs=len(mem_indices)
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
if storage_indices == []:
|
|
730
|
+
break
|
|
731
|
+
|
|
732
|
+
mem_indices_flat.extend(mem_indices)
|
|
733
|
+
storage_indices_flat.extend(storage_indices)
|
|
734
|
+
|
|
735
|
+
stored_keys.append(key)
|
|
736
|
+
storage_objs.append(
|
|
737
|
+
NixlStoreObj(
|
|
738
|
+
page_indices=storage_indices,
|
|
739
|
+
size=obj.meta.phy_size,
|
|
740
|
+
layout=MemoryLayoutDesc(
|
|
741
|
+
[obj.meta.shape],
|
|
742
|
+
[obj.meta.dtype],
|
|
743
|
+
),
|
|
744
|
+
pin_count=1,
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
if not mem_indices_flat:
|
|
749
|
+
# Nothing to store (all keys already existed or pool empty)
|
|
750
|
+
with self._lock:
|
|
751
|
+
self._completed_store_tasks[task_id] = True
|
|
752
|
+
self._signal_store_event()
|
|
753
|
+
return
|
|
754
|
+
|
|
755
|
+
handle = self.nixl_agent.get_mem_to_storage_handle(
|
|
756
|
+
mem_indices_flat,
|
|
757
|
+
storage_indices_flat,
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
await self.nixl_agent.post_non_blocking(handle)
|
|
761
|
+
self.nixl_agent.release_handle(handle)
|
|
762
|
+
|
|
763
|
+
with self._lock:
|
|
764
|
+
for key, storage_obj in zip(stored_keys, storage_objs, strict=False):
|
|
765
|
+
self._memory_objects[key] = storage_obj
|
|
766
|
+
storage_obj.decrease_pin_count()
|
|
767
|
+
self._notify_keys_stored(stored_keys)
|
|
768
|
+
|
|
769
|
+
# success is only set to false for transfer failures
|
|
770
|
+
except Exception:
|
|
771
|
+
logger.exception("NIXL store task %d failed", task_id)
|
|
772
|
+
success = False
|
|
773
|
+
|
|
774
|
+
# free storage indices if transfer fails
|
|
775
|
+
self.nixl_agent.pool.batched_free(storage_indices_flat)
|
|
776
|
+
|
|
777
|
+
with self._lock:
|
|
778
|
+
self._completed_store_tasks[task_id] = success
|
|
779
|
+
|
|
780
|
+
self._signal_store_event()
|
|
781
|
+
|
|
782
|
+
def _signal_lookup_event(self) -> None:
|
|
783
|
+
"""Signal the lookup event fd to notify completion."""
|
|
784
|
+
os.eventfd_write(self._lookup_efd, 1)
|
|
785
|
+
|
|
786
|
+
def _execute_lookup_in_the_loop(
|
|
787
|
+
self, keys: list[ObjectKey], task_id: L2TaskId
|
|
788
|
+
) -> None:
|
|
789
|
+
"""
|
|
790
|
+
Performs a batched lookup and pin in the event loop.
|
|
791
|
+
|
|
792
|
+
For each key present in ``_memory_objects``, its bit is set in the
|
|
793
|
+
result bitmap and its pin count is incremented to prevent eviction
|
|
794
|
+
while the caller holds the lock. Keys not found are left unset.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
keys: Keys to look up.
|
|
798
|
+
task_id: Identifier used to report completion via
|
|
799
|
+
``_completed_lookup_tasks``.
|
|
800
|
+
"""
|
|
801
|
+
bitmap = Bitmap(len(keys))
|
|
802
|
+
with self._lock:
|
|
803
|
+
for i, key in enumerate(keys):
|
|
804
|
+
if (obj := self._memory_objects.get(key)) is None:
|
|
805
|
+
continue
|
|
806
|
+
bitmap.set(i)
|
|
807
|
+
obj.increase_pin_count()
|
|
808
|
+
self._completed_lookup_tasks[task_id] = bitmap
|
|
809
|
+
self._signal_lookup_event()
|
|
810
|
+
|
|
811
|
+
def _signal_load_event(self) -> None:
|
|
812
|
+
"""Signal the load event fd to notify completion."""
|
|
813
|
+
os.eventfd_write(self._load_efd, 1)
|
|
814
|
+
|
|
815
|
+
async def _execute_load_in_loop(
|
|
816
|
+
self,
|
|
817
|
+
keys: list[ObjectKey],
|
|
818
|
+
objects: list[MemoryObj],
|
|
819
|
+
task_id: L2TaskId,
|
|
820
|
+
) -> None:
|
|
821
|
+
"""
|
|
822
|
+
Coroutine that performs a batched load from Nixl storage into L1 memory.
|
|
823
|
+
|
|
824
|
+
For each key that exists in ``_memory_objects``, the corresponding
|
|
825
|
+
storage page indices are gathered and a single batched DMA read is
|
|
826
|
+
issued into the provided memory objects. Keys that are not found are
|
|
827
|
+
silently skipped and their bit in the result bitmap is left unset.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
keys: Keys identifying each object to load.
|
|
831
|
+
objects: Pre-allocated memory objects that will receive the loaded
|
|
832
|
+
data. Must be the same length as ``keys``.
|
|
833
|
+
task_id: Identifier used to report completion via
|
|
834
|
+
``_completed_load_tasks``.
|
|
835
|
+
"""
|
|
836
|
+
bitmap = Bitmap(len(keys))
|
|
837
|
+
accessed_keys: list[ObjectKey] = []
|
|
838
|
+
try:
|
|
839
|
+
mem_indices_flat = []
|
|
840
|
+
storage_indices_flat = []
|
|
841
|
+
|
|
842
|
+
with self._lock:
|
|
843
|
+
for i, key in enumerate(keys):
|
|
844
|
+
if (storage_obj := self._memory_objects.get(key)) is None:
|
|
845
|
+
continue
|
|
846
|
+
mem_addr = objects[i].meta.address
|
|
847
|
+
mem_size = objects[i].meta.phy_size
|
|
848
|
+
mem_indices = self.nixl_agent.get_memory_indices(mem_addr, mem_size)
|
|
849
|
+
|
|
850
|
+
mem_indices_flat.extend(mem_indices)
|
|
851
|
+
storage_indices_flat.extend(storage_obj.page_indices)
|
|
852
|
+
|
|
853
|
+
bitmap.set(i)
|
|
854
|
+
accessed_keys.append(key)
|
|
855
|
+
|
|
856
|
+
if mem_indices_flat:
|
|
857
|
+
handle = self.nixl_agent.get_storage_to_mem_handle(
|
|
858
|
+
mem_indices_flat,
|
|
859
|
+
storage_indices_flat,
|
|
860
|
+
)
|
|
861
|
+
await self.nixl_agent.post_non_blocking(handle)
|
|
862
|
+
self.nixl_agent.release_handle(handle)
|
|
863
|
+
except Exception:
|
|
864
|
+
logger.exception("NIXL load task %d failed", task_id)
|
|
865
|
+
|
|
866
|
+
if accessed_keys:
|
|
867
|
+
self._notify_keys_accessed(accessed_keys)
|
|
868
|
+
with self._lock:
|
|
869
|
+
self._completed_load_tasks[task_id] = bitmap
|
|
870
|
+
self._signal_load_event()
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
# ---------------------------------------------------------------------
|
|
874
|
+
# Config and self-registration
|
|
875
|
+
# ---------------------------------------------------------------------
|
|
876
|
+
|
|
877
|
+
_VALID_NIXL_BACKENDS = (
|
|
878
|
+
"GDS",
|
|
879
|
+
"GDS_MT",
|
|
880
|
+
"POSIX",
|
|
881
|
+
"HF3FS",
|
|
882
|
+
"OBJ",
|
|
883
|
+
)
|
|
884
|
+
_FILE_BACKENDS = ("GDS", "GDS_MT", "POSIX", "HF3FS")
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
class NixlStoreL2AdapterConfig(L2AdapterConfigBase):
|
|
888
|
+
"""
|
|
889
|
+
Config for a Nixl-store-based L2 adapter.
|
|
890
|
+
|
|
891
|
+
Fields:
|
|
892
|
+
- backend: Nixl storage backend
|
|
893
|
+
(GDS, GDS_MT, POSIX, HF3FS, OBJ).
|
|
894
|
+
- backend_params: Backend-specific parameters as a
|
|
895
|
+
dict of string key-value pairs. For file-based
|
|
896
|
+
backends (GDS, GDS_MT, POSIX, HF3FS), must include
|
|
897
|
+
``file_path``. May also include ``use_direct_io``
|
|
898
|
+
(default ``"false"``) and other backend-specific
|
|
899
|
+
keys.
|
|
900
|
+
- pool_size: Number of storage descriptors to
|
|
901
|
+
pre-allocate (must be > 0).
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
def __init__(
|
|
905
|
+
self,
|
|
906
|
+
backend: str,
|
|
907
|
+
backend_params: dict[str, str],
|
|
908
|
+
pool_size: int,
|
|
909
|
+
):
|
|
910
|
+
if backend in _FILE_BACKENDS:
|
|
911
|
+
if "file_path" not in backend_params:
|
|
912
|
+
raise ValueError(
|
|
913
|
+
"backend_params must include "
|
|
914
|
+
"'file_path' for file-based "
|
|
915
|
+
"backend %r" % backend
|
|
916
|
+
)
|
|
917
|
+
if "use_direct_io" not in backend_params:
|
|
918
|
+
raise ValueError(
|
|
919
|
+
"backend_params must include "
|
|
920
|
+
"'use_direct_io' for file-based "
|
|
921
|
+
"backend %r" % backend
|
|
922
|
+
)
|
|
923
|
+
self.backend = backend
|
|
924
|
+
self.backend_params = backend_params
|
|
925
|
+
self.pool_size = pool_size
|
|
926
|
+
|
|
927
|
+
@classmethod
|
|
928
|
+
def from_dict(cls, d: dict) -> "NixlStoreL2AdapterConfig":
|
|
929
|
+
backend = d.get("backend")
|
|
930
|
+
if backend not in _VALID_NIXL_BACKENDS:
|
|
931
|
+
raise ValueError(
|
|
932
|
+
"backend must be one of %s, got %r" % (_VALID_NIXL_BACKENDS, backend)
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
backend_params = d.get("backend_params", {})
|
|
936
|
+
if not isinstance(backend_params, dict):
|
|
937
|
+
raise ValueError("backend_params must be a dict of string key-value pairs")
|
|
938
|
+
|
|
939
|
+
pool_size = d.get("pool_size")
|
|
940
|
+
if not isinstance(pool_size, int) or pool_size <= 0:
|
|
941
|
+
raise ValueError("pool_size must be a positive integer")
|
|
942
|
+
|
|
943
|
+
return cls(
|
|
944
|
+
backend=backend,
|
|
945
|
+
backend_params=backend_params,
|
|
946
|
+
pool_size=pool_size,
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
@classmethod
|
|
950
|
+
def help(cls) -> str:
|
|
951
|
+
return (
|
|
952
|
+
"Nixl store L2 adapter config fields:\n"
|
|
953
|
+
"- backend (str): Nixl storage backend, "
|
|
954
|
+
"one of %s (required)\n"
|
|
955
|
+
"- backend_params (dict): backend-specific "
|
|
956
|
+
"string key-value pairs (optional, "
|
|
957
|
+
"default empty). File-based backends "
|
|
958
|
+
"require file_path. Optional keys include "
|
|
959
|
+
"'use_direct_io' (default 'false') and "
|
|
960
|
+
"'file_size' (int, size in bytes of each "
|
|
961
|
+
"storage file slot; defaults to the L1 "
|
|
962
|
+
"page size if not set).\n"
|
|
963
|
+
"- pool_size (int): number of storage "
|
|
964
|
+
"descriptors to pre-allocate (required, "
|
|
965
|
+
">0)" % (_VALID_NIXL_BACKENDS,)
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
# Self-register config type and adapter factory
|
|
970
|
+
register_l2_adapter_type("nixl_store", NixlStoreL2AdapterConfig)
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def _create_nixl_store_adapter(
|
|
974
|
+
config: L2AdapterConfigBase,
|
|
975
|
+
l1_memory_desc: Optional[L1MemoryDesc] = None,
|
|
976
|
+
) -> L2AdapterInterface:
|
|
977
|
+
"""Create a NixlStoreL2Adapter from config."""
|
|
978
|
+
if l1_memory_desc is None:
|
|
979
|
+
raise ValueError("l1_memory_desc is required to create a NixlStoreL2Adapter.")
|
|
980
|
+
return NixlStoreL2Adapter(config, l1_memory_desc) # type: ignore[arg-type]
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
register_l2_adapter_factory("nixl_store", _create_nixl_store_adapter)
|