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,2619 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from collections import deque
|
|
4
|
+
from contextlib import nullcontext
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum, auto
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
9
|
+
import abc
|
|
10
|
+
import ctypes
|
|
11
|
+
import os
|
|
12
|
+
import threading
|
|
13
|
+
|
|
14
|
+
# Third Party
|
|
15
|
+
from sortedcontainers import SortedList
|
|
16
|
+
import torch
|
|
17
|
+
|
|
18
|
+
# First Party
|
|
19
|
+
from lmcache.integration.vllm.utils import get_size_bytes
|
|
20
|
+
from lmcache.logging import init_logger
|
|
21
|
+
from lmcache.observability import LMCStatsMonitor
|
|
22
|
+
from lmcache.utils import _lmcache_nvtx_annotate
|
|
23
|
+
from lmcache.v1.pin_monitor import PinMonitor
|
|
24
|
+
from lmcache.v1.system_detection import NUMAMapping
|
|
25
|
+
import lmcache.c_ops as lmc_ops
|
|
26
|
+
|
|
27
|
+
logger = init_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Helper functions for thread safety
|
|
31
|
+
def synchronized(lock_attr_name):
|
|
32
|
+
"""
|
|
33
|
+
Decorator to make a method thread-safe by acquiring the lock
|
|
34
|
+
specified by lock_attr_name on the instance.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def decorator(method):
|
|
38
|
+
@wraps(method)
|
|
39
|
+
def wrapper(self, *args, **kwargs):
|
|
40
|
+
lock = getattr(self, lock_attr_name)
|
|
41
|
+
with lock:
|
|
42
|
+
return method(self, *args, **kwargs)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
return decorator
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MemoryFormat(Enum):
|
|
50
|
+
UNDEFINED = 0
|
|
51
|
+
"""[2, num_layers, num_tokens, hidden_dim]
|
|
52
|
+
"""
|
|
53
|
+
# KV_BLOB = 1
|
|
54
|
+
KV_2LTD = auto()
|
|
55
|
+
"""[num_tokens, 2, hidden_dim]
|
|
56
|
+
"""
|
|
57
|
+
# LAYER_KV_BLOB = 2
|
|
58
|
+
KV_T2D = auto()
|
|
59
|
+
"""[2, num_tokens, hidden_dim]
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
KV_2TD = auto()
|
|
63
|
+
"""Compressed binary array format
|
|
64
|
+
"""
|
|
65
|
+
BINARY = auto()
|
|
66
|
+
|
|
67
|
+
BINARY_BUFFER = auto()
|
|
68
|
+
|
|
69
|
+
KV_MLA_FMT = auto()
|
|
70
|
+
"""[1, num_layers, num_tokens, aligned_head_size]
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def token_dim(self) -> int:
|
|
74
|
+
if self == MemoryFormat.KV_2LTD:
|
|
75
|
+
return 2
|
|
76
|
+
elif self == MemoryFormat.KV_T2D:
|
|
77
|
+
return 1
|
|
78
|
+
elif self == MemoryFormat.KV_2TD:
|
|
79
|
+
return 0
|
|
80
|
+
elif self == MemoryFormat.BINARY:
|
|
81
|
+
return 0
|
|
82
|
+
elif self == MemoryFormat.BINARY_BUFFER:
|
|
83
|
+
return 0
|
|
84
|
+
elif self == MemoryFormat.KV_MLA_FMT:
|
|
85
|
+
return 2
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class FreeBlock:
|
|
91
|
+
"""Metadata class used by the memory allocators"""
|
|
92
|
+
|
|
93
|
+
start: int
|
|
94
|
+
size: int
|
|
95
|
+
|
|
96
|
+
def can_be_coalesced(self, succ: "FreeBlock") -> bool:
|
|
97
|
+
return self.start + self.size == succ.start
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class MemoryObjMetadata:
|
|
102
|
+
# TODO(chunxiaozheng): use shapes and dtypes to replace shape and dtype
|
|
103
|
+
# The 'logical' shape of the tensor
|
|
104
|
+
shape: torch.Size
|
|
105
|
+
|
|
106
|
+
# The 'logical' dtype of the tensor
|
|
107
|
+
dtype: Optional[torch.dtype]
|
|
108
|
+
|
|
109
|
+
# The 'physical address' of the tensor
|
|
110
|
+
address: int
|
|
111
|
+
|
|
112
|
+
# The 'physical size' in bytes of the allocated memory
|
|
113
|
+
phy_size: int
|
|
114
|
+
|
|
115
|
+
# Reference count
|
|
116
|
+
ref_count: int
|
|
117
|
+
|
|
118
|
+
# Whether the object is pinned and cannot be evicted
|
|
119
|
+
# lookup pins are temporary
|
|
120
|
+
# cache controller pins are persistent
|
|
121
|
+
pin_count: int = 0
|
|
122
|
+
|
|
123
|
+
# The 'logical' format of the tensor
|
|
124
|
+
fmt: MemoryFormat = MemoryFormat.UNDEFINED
|
|
125
|
+
|
|
126
|
+
# Positions when the cache is stored
|
|
127
|
+
cached_positions: Optional[torch.Tensor] = None
|
|
128
|
+
|
|
129
|
+
# shapes and dtypes should be used in the future
|
|
130
|
+
shapes: Optional[list[torch.Size]] = None
|
|
131
|
+
dtypes: Optional[list[torch.dtype]] = None
|
|
132
|
+
|
|
133
|
+
def to_dict(self):
|
|
134
|
+
# Note(Kuntai): this is used for serializing MemoryObjMetadata via
|
|
135
|
+
# msgpack.
|
|
136
|
+
return {
|
|
137
|
+
"__type__": "MemoryObjMetadata",
|
|
138
|
+
"shape": list(self.shape), # torch.Size -> list
|
|
139
|
+
"dtype": str(self.dtype) if self.dtype else None,
|
|
140
|
+
"address": self.address,
|
|
141
|
+
"phy_size": self.phy_size,
|
|
142
|
+
"ref_count": self.ref_count,
|
|
143
|
+
"fmt": self.fmt.value,
|
|
144
|
+
"shapes": [list(shape) for shape in self.shapes] if self.shapes else None,
|
|
145
|
+
"dtypes": [str(dtype) for dtype in self.dtypes] if self.dtypes else None,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def from_dict(d):
|
|
150
|
+
dtype_str = d["dtype"]
|
|
151
|
+
dtype = getattr(torch, dtype_str.replace("torch.", "")) if dtype_str else None
|
|
152
|
+
shapes_list = d["shapes"]
|
|
153
|
+
shapes = [torch.Size(s) for s in shapes_list] if shapes_list else None
|
|
154
|
+
dtypes_list = d["dtypes"]
|
|
155
|
+
dtypes = (
|
|
156
|
+
[getattr(torch, d_str.replace("torch.", "")) for d_str in dtypes_list]
|
|
157
|
+
if dtypes_list
|
|
158
|
+
else None
|
|
159
|
+
)
|
|
160
|
+
return MemoryObjMetadata(
|
|
161
|
+
shape=torch.Size(d["shape"]),
|
|
162
|
+
dtype=dtype,
|
|
163
|
+
address=d["address"],
|
|
164
|
+
phy_size=d["phy_size"],
|
|
165
|
+
ref_count=d["ref_count"],
|
|
166
|
+
fmt=MemoryFormat(d["fmt"]),
|
|
167
|
+
shapes=shapes,
|
|
168
|
+
dtypes=dtypes,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def get_size(self) -> int:
|
|
172
|
+
if self.shapes is not None and self.dtypes is not None:
|
|
173
|
+
return get_size_bytes(self.shapes, self.dtypes)
|
|
174
|
+
return self.shape.numel() * self.dtype.itemsize # type: ignore
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MemoryObj(metaclass=abc.ABCMeta):
|
|
178
|
+
"""
|
|
179
|
+
MemoryObj interface.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
# subclasses should expose raw_data differently
|
|
183
|
+
raw_data: Any
|
|
184
|
+
|
|
185
|
+
def __init__(self, metadata: MemoryObjMetadata):
|
|
186
|
+
self.meta = metadata
|
|
187
|
+
|
|
188
|
+
@abc.abstractmethod
|
|
189
|
+
def invalidate(self):
|
|
190
|
+
"""
|
|
191
|
+
Invalidate the MemoryObj.
|
|
192
|
+
"""
|
|
193
|
+
raise NotImplementedError
|
|
194
|
+
|
|
195
|
+
@abc.abstractmethod
|
|
196
|
+
def is_valid(self):
|
|
197
|
+
"""
|
|
198
|
+
Check if the MemoryObj is valid.
|
|
199
|
+
"""
|
|
200
|
+
raise NotImplementedError
|
|
201
|
+
|
|
202
|
+
@abc.abstractmethod
|
|
203
|
+
def get_size(self) -> int:
|
|
204
|
+
"""
|
|
205
|
+
Get the size of the MemoryObj in bytes.
|
|
206
|
+
Note that this number could be smaller than the physical size.
|
|
207
|
+
The physical size is aligned to the allocator's alignment.
|
|
208
|
+
"""
|
|
209
|
+
raise NotImplementedError
|
|
210
|
+
|
|
211
|
+
@abc.abstractmethod
|
|
212
|
+
def get_shape(self) -> torch.Size:
|
|
213
|
+
"""
|
|
214
|
+
Get the shape of the MemoryObj.
|
|
215
|
+
"""
|
|
216
|
+
raise NotImplementedError
|
|
217
|
+
|
|
218
|
+
def get_dtype(self) -> Optional[torch.dtype]:
|
|
219
|
+
"""
|
|
220
|
+
Get the dtype of the MemoryObj.
|
|
221
|
+
"""
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
@abc.abstractmethod
|
|
225
|
+
def get_shapes(self) -> list[torch.Size]:
|
|
226
|
+
"""
|
|
227
|
+
Get the shapes of the MemoryObj.
|
|
228
|
+
"""
|
|
229
|
+
raise NotImplementedError
|
|
230
|
+
|
|
231
|
+
@abc.abstractmethod
|
|
232
|
+
def get_dtypes(self) -> list[torch.dtype]:
|
|
233
|
+
"""
|
|
234
|
+
Get the dtypes of the MemoryObj.
|
|
235
|
+
"""
|
|
236
|
+
raise NotImplementedError
|
|
237
|
+
|
|
238
|
+
@abc.abstractmethod
|
|
239
|
+
def get_memory_format(self) -> MemoryFormat:
|
|
240
|
+
"""
|
|
241
|
+
Get the memory format of the MemoryObj.
|
|
242
|
+
"""
|
|
243
|
+
raise NotImplementedError
|
|
244
|
+
|
|
245
|
+
@abc.abstractmethod
|
|
246
|
+
def get_physical_size(self) -> int:
|
|
247
|
+
"""
|
|
248
|
+
Get the physical size of the MemoryObj in bytes.
|
|
249
|
+
"""
|
|
250
|
+
raise NotImplementedError
|
|
251
|
+
|
|
252
|
+
@abc.abstractmethod
|
|
253
|
+
def pin(self) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Pin the memory obj so that it will not be evicted.
|
|
256
|
+
"""
|
|
257
|
+
raise NotImplementedError
|
|
258
|
+
|
|
259
|
+
@abc.abstractmethod
|
|
260
|
+
def ref_count_up(self):
|
|
261
|
+
"""
|
|
262
|
+
Increase ref count for the given MemoryObj by one.
|
|
263
|
+
"""
|
|
264
|
+
raise NotImplementedError
|
|
265
|
+
|
|
266
|
+
@abc.abstractmethod
|
|
267
|
+
def unpin(self) -> bool:
|
|
268
|
+
"""
|
|
269
|
+
Unpin the memory obj so that it can be evicted.
|
|
270
|
+
"""
|
|
271
|
+
raise NotImplementedError
|
|
272
|
+
|
|
273
|
+
@abc.abstractmethod
|
|
274
|
+
def ref_count_down(self):
|
|
275
|
+
"""
|
|
276
|
+
Decrease ref count for the given MemoryObj by one.
|
|
277
|
+
"""
|
|
278
|
+
raise NotImplementedError
|
|
279
|
+
|
|
280
|
+
@abc.abstractmethod
|
|
281
|
+
def get_ref_count(self) -> int:
|
|
282
|
+
"""
|
|
283
|
+
Get ref count for the given MemoryObj.
|
|
284
|
+
"""
|
|
285
|
+
raise NotImplementedError
|
|
286
|
+
|
|
287
|
+
@abc.abstractmethod
|
|
288
|
+
def get_num_tokens(self) -> int:
|
|
289
|
+
"""
|
|
290
|
+
Get token number for the given MemoryObj.
|
|
291
|
+
"""
|
|
292
|
+
raise NotImplementedError
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
@abc.abstractmethod
|
|
296
|
+
def metadata(self) -> MemoryObjMetadata:
|
|
297
|
+
"""
|
|
298
|
+
Get the metada of the MemoryObj.
|
|
299
|
+
"""
|
|
300
|
+
raise NotImplementedError
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
@abc.abstractmethod
|
|
304
|
+
def tensor(self) -> Optional[torch.Tensor]:
|
|
305
|
+
"""
|
|
306
|
+
Get the tensor from the MemoryObj.
|
|
307
|
+
"""
|
|
308
|
+
raise NotImplementedError
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
@abc.abstractmethod
|
|
312
|
+
def byte_array(self) -> bytes:
|
|
313
|
+
"""
|
|
314
|
+
Get the byte array from the MemoryObj.
|
|
315
|
+
The size is will be the physical size instead of the unaligned size.
|
|
316
|
+
"""
|
|
317
|
+
raise NotImplementedError
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
@abc.abstractmethod
|
|
321
|
+
def data_ptr(self) -> int:
|
|
322
|
+
"""
|
|
323
|
+
Get the data pointer of the MemoryObj.
|
|
324
|
+
This is used to access the raw data in the memory.
|
|
325
|
+
"""
|
|
326
|
+
raise NotImplementedError
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
@abc.abstractmethod
|
|
330
|
+
def is_pinned(self) -> bool:
|
|
331
|
+
"""
|
|
332
|
+
Check whether the memory obj is pinned.
|
|
333
|
+
"""
|
|
334
|
+
raise NotImplementedError
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
@abc.abstractmethod
|
|
338
|
+
def can_evict(self) -> bool:
|
|
339
|
+
"""
|
|
340
|
+
Check whether the memory obj can be evicted.
|
|
341
|
+
"""
|
|
342
|
+
raise NotImplementedError
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
@abc.abstractmethod
|
|
346
|
+
def raw_tensor(self) -> Optional[torch.Tensor]:
|
|
347
|
+
"""
|
|
348
|
+
Get the raw tensor from the MemoryObj.
|
|
349
|
+
"""
|
|
350
|
+
raise NotImplementedError
|
|
351
|
+
|
|
352
|
+
@abc.abstractmethod
|
|
353
|
+
def get_tensor(self, index: int) -> Optional[torch.Tensor]:
|
|
354
|
+
"""
|
|
355
|
+
Get the tensor from the MemoryObj at the given index(group).
|
|
356
|
+
"""
|
|
357
|
+
raise NotImplementedError
|
|
358
|
+
|
|
359
|
+
@abc.abstractmethod
|
|
360
|
+
def parent(self) -> Optional["MemoryAllocatorInterface"]:
|
|
361
|
+
"""
|
|
362
|
+
Get the allocator that allocates this memory object
|
|
363
|
+
"""
|
|
364
|
+
raise NotImplementedError
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _resolve_pinned_alloc_free(
|
|
368
|
+
numa_mapping: Optional[NUMAMapping] = None,
|
|
369
|
+
shm_name: Optional[str] = None,
|
|
370
|
+
size: Optional[int] = None,
|
|
371
|
+
) -> Tuple[
|
|
372
|
+
tuple, # (alloc_fn, *alloc_args)
|
|
373
|
+
tuple, # (free_fn, *free_args_after_ptr)
|
|
374
|
+
]:
|
|
375
|
+
"""Resolve the alloc/free function pair based on memory type.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
A tuple of (alloc_info, free_info) where:
|
|
379
|
+
- alloc_info: (alloc_fn, *args) to call as alloc_fn(size, *args)
|
|
380
|
+
- free_info: (free_fn, *args) to call as free_fn(ptr, *args)
|
|
381
|
+
"""
|
|
382
|
+
if shm_name:
|
|
383
|
+
return (
|
|
384
|
+
(lmc_ops.alloc_shm_pinned_ptr, shm_name),
|
|
385
|
+
(lmc_ops.free_shm_pinned_ptr, size, shm_name),
|
|
386
|
+
)
|
|
387
|
+
elif numa_mapping:
|
|
388
|
+
if torch.cuda.is_available():
|
|
389
|
+
current_device_id = torch.cuda.current_device()
|
|
390
|
+
else:
|
|
391
|
+
current_device_id = 0
|
|
392
|
+
gpu_to_numa_mapping = numa_mapping.gpu_to_numa_mapping
|
|
393
|
+
assert current_device_id in gpu_to_numa_mapping, (
|
|
394
|
+
f"Current device {current_device_id} is not in the GPU NUMA mapping."
|
|
395
|
+
)
|
|
396
|
+
numa_id = gpu_to_numa_mapping[current_device_id]
|
|
397
|
+
return (
|
|
398
|
+
(lmc_ops.alloc_pinned_numa_ptr, numa_id),
|
|
399
|
+
(lmc_ops.free_pinned_numa_ptr, size),
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
return (
|
|
403
|
+
(lmc_ops.alloc_pinned_ptr, 0),
|
|
404
|
+
(lmc_ops.free_pinned_ptr,),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _allocate_cpu_memory(
|
|
409
|
+
size: int,
|
|
410
|
+
numa_mapping: Optional[NUMAMapping] = None,
|
|
411
|
+
shm_name: Optional[str] = None,
|
|
412
|
+
) -> torch.Tensor:
|
|
413
|
+
if size == 0:
|
|
414
|
+
return torch.empty(0, dtype=torch.uint8)
|
|
415
|
+
|
|
416
|
+
alloc_info, _ = _resolve_pinned_alloc_free(
|
|
417
|
+
numa_mapping,
|
|
418
|
+
shm_name,
|
|
419
|
+
)
|
|
420
|
+
alloc_fn, *alloc_args = alloc_info
|
|
421
|
+
ptr = alloc_fn(size, *alloc_args)
|
|
422
|
+
|
|
423
|
+
array_type = ctypes.c_uint8 * size
|
|
424
|
+
buf = array_type.from_address(ptr)
|
|
425
|
+
buffer = torch.frombuffer(buf, dtype=torch.uint8)
|
|
426
|
+
|
|
427
|
+
return buffer
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _free_cpu_memory(
|
|
431
|
+
buffer: torch.Tensor,
|
|
432
|
+
size: int | None = None,
|
|
433
|
+
numa_mapping: Optional[NUMAMapping] = None,
|
|
434
|
+
shm_name: Optional[str] = None,
|
|
435
|
+
) -> None:
|
|
436
|
+
if torch.cuda.is_available():
|
|
437
|
+
torch.cuda.synchronize()
|
|
438
|
+
|
|
439
|
+
_, free_info = _resolve_pinned_alloc_free(
|
|
440
|
+
numa_mapping,
|
|
441
|
+
shm_name,
|
|
442
|
+
size=size,
|
|
443
|
+
)
|
|
444
|
+
free_fn, *free_args = free_info
|
|
445
|
+
free_fn(buffer.data_ptr(), *free_args)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _allocate_gpu_memory(
|
|
449
|
+
size: int,
|
|
450
|
+
device: str,
|
|
451
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
452
|
+
page_size = os.sysconf("SC_PAGESIZE")
|
|
453
|
+
|
|
454
|
+
# Over-allocate
|
|
455
|
+
base_buffer = torch.empty(size + page_size, dtype=torch.uint8, device=device)
|
|
456
|
+
offset = -base_buffer.data_ptr() % page_size
|
|
457
|
+
|
|
458
|
+
# Make aligned view
|
|
459
|
+
aligned_buffer = base_buffer[offset : offset + size]
|
|
460
|
+
|
|
461
|
+
# Need to return the base buffer as well in order to prevent GC
|
|
462
|
+
return base_buffer, aligned_buffer
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class TensorMemoryObj(MemoryObj):
|
|
466
|
+
"""
|
|
467
|
+
Wraps a raw flat tensor with some metadata
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
monitor = LMCStatsMonitor.GetOrCreate()
|
|
471
|
+
|
|
472
|
+
def __init__(
|
|
473
|
+
self,
|
|
474
|
+
raw_data: torch.Tensor,
|
|
475
|
+
metadata: MemoryObjMetadata,
|
|
476
|
+
parent_allocator: Optional["MemoryAllocatorInterface"],
|
|
477
|
+
):
|
|
478
|
+
assert metadata.dtype is not None, "dtype must be specified for TensorMemoryObj"
|
|
479
|
+
super().__init__(metadata)
|
|
480
|
+
self.raw_data = raw_data
|
|
481
|
+
self.valid = True
|
|
482
|
+
self.lock = threading.Lock()
|
|
483
|
+
self.parent_allocator = parent_allocator
|
|
484
|
+
# Calculate the prefix sum of the group sizes
|
|
485
|
+
# If there are two groups, the prefix sum will be
|
|
486
|
+
# [0, size_of_group_1, size_of_group_1 + size_of_group_2]
|
|
487
|
+
self.group_prefix_sum = [0]
|
|
488
|
+
if self.meta.shapes is not None and self.meta.dtypes is not None:
|
|
489
|
+
size_in_bytes = 0
|
|
490
|
+
for shape, dtype in zip(self.meta.shapes, self.meta.dtypes, strict=True):
|
|
491
|
+
size_in_bytes += shape.numel() * dtype.itemsize
|
|
492
|
+
self.group_prefix_sum.append(size_in_bytes)
|
|
493
|
+
else:
|
|
494
|
+
self.group_prefix_sum.append(self.meta.get_size())
|
|
495
|
+
|
|
496
|
+
def __del__(self):
|
|
497
|
+
"""
|
|
498
|
+
Destructor to ensure memory is released when the object is garbage collected.
|
|
499
|
+
This acts as a safety net to prevent memory leaks if ref_count_down() is not
|
|
500
|
+
called properly somewhere in the code path.
|
|
501
|
+
"""
|
|
502
|
+
if self.parent_allocator is not None and self.is_valid():
|
|
503
|
+
if self.meta.ref_count > 0 or self.meta.pin_count > 0:
|
|
504
|
+
logger.warning(
|
|
505
|
+
"MemoryObj at %s is being garbage collected "
|
|
506
|
+
"with ref_count=%d, pin_count=%d. "
|
|
507
|
+
"This indicates ref_count_down()/unpin() was not called properly.",
|
|
508
|
+
self.meta.address,
|
|
509
|
+
self.meta.ref_count,
|
|
510
|
+
self.meta.pin_count,
|
|
511
|
+
)
|
|
512
|
+
self.parent_allocator.free(self)
|
|
513
|
+
|
|
514
|
+
def invalidate(self):
|
|
515
|
+
self.valid = False
|
|
516
|
+
|
|
517
|
+
def is_valid(self):
|
|
518
|
+
return self.valid
|
|
519
|
+
|
|
520
|
+
def get_size(self) -> int:
|
|
521
|
+
return self.group_prefix_sum[-1]
|
|
522
|
+
|
|
523
|
+
# TODO(chunxiaozheng): use get_shapes and get_dtypes to replace
|
|
524
|
+
# get_shape and get_dtype
|
|
525
|
+
def get_shape(self) -> torch.Size:
|
|
526
|
+
return self.meta.shape
|
|
527
|
+
|
|
528
|
+
def get_dtype(self) -> torch.dtype:
|
|
529
|
+
return self.meta.dtype
|
|
530
|
+
|
|
531
|
+
def get_shapes(self) -> list[torch.Size]:
|
|
532
|
+
assert self.meta.shapes is not None
|
|
533
|
+
return self.meta.shapes
|
|
534
|
+
|
|
535
|
+
def get_dtypes(self) -> list[torch.dtype]:
|
|
536
|
+
assert self.meta.dtypes is not None
|
|
537
|
+
return self.meta.dtypes
|
|
538
|
+
|
|
539
|
+
def get_memory_format(self) -> MemoryFormat:
|
|
540
|
+
with self.lock:
|
|
541
|
+
return self.meta.fmt
|
|
542
|
+
|
|
543
|
+
def get_physical_size(self) -> int:
|
|
544
|
+
return self.meta.phy_size
|
|
545
|
+
|
|
546
|
+
def ref_count_up(self):
|
|
547
|
+
with self.lock:
|
|
548
|
+
self.meta.ref_count += 1
|
|
549
|
+
|
|
550
|
+
def ref_count_down(self):
|
|
551
|
+
with self.lock:
|
|
552
|
+
self.meta.ref_count -= 1
|
|
553
|
+
if self.meta.ref_count < 0:
|
|
554
|
+
logger.warning(
|
|
555
|
+
f"Ref count of MemoryObj {self.meta.address}"
|
|
556
|
+
f"is negative: {self.meta.ref_count}."
|
|
557
|
+
"Double free occurred somewhere."
|
|
558
|
+
"Setting ref count back to 0 as a hack but please find the bug."
|
|
559
|
+
)
|
|
560
|
+
self.meta.ref_count = 0
|
|
561
|
+
if (
|
|
562
|
+
self.meta.ref_count == 0
|
|
563
|
+
and self.parent_allocator is not None
|
|
564
|
+
and self.meta.pin_count == 0
|
|
565
|
+
):
|
|
566
|
+
self.parent_allocator.free(self)
|
|
567
|
+
|
|
568
|
+
def get_ref_count(self) -> int:
|
|
569
|
+
with self.lock:
|
|
570
|
+
return self.meta.ref_count
|
|
571
|
+
|
|
572
|
+
def get_num_tokens(self) -> int:
|
|
573
|
+
with self.lock:
|
|
574
|
+
token_dim = self.meta.fmt.token_dim()
|
|
575
|
+
return self.meta.shape[token_dim]
|
|
576
|
+
|
|
577
|
+
def pin(self) -> bool:
|
|
578
|
+
with self.lock:
|
|
579
|
+
# if pin_count is 0, indicates that the object is pinned for the first time
|
|
580
|
+
if self.meta.pin_count == 0:
|
|
581
|
+
TensorMemoryObj.monitor.update_pinned_memory_objs_count(1)
|
|
582
|
+
|
|
583
|
+
self.meta.pin_count += 1
|
|
584
|
+
|
|
585
|
+
# Register/update with PinMonitor for timeout tracking on every pin
|
|
586
|
+
pin_monitor = PinMonitor.GetOrCreate()
|
|
587
|
+
pin_monitor.on_pin(self)
|
|
588
|
+
return True
|
|
589
|
+
|
|
590
|
+
def unpin(self) -> bool:
|
|
591
|
+
with self.lock:
|
|
592
|
+
self.meta.pin_count -= 1
|
|
593
|
+
|
|
594
|
+
# if pin_count is 0, indicates that the object is unpinned
|
|
595
|
+
if self.meta.pin_count == 0:
|
|
596
|
+
TensorMemoryObj.monitor.update_pinned_memory_objs_count(-1)
|
|
597
|
+
# Unregister from PinMonitor when fully unpinned
|
|
598
|
+
pin_monitor = PinMonitor.GetOrCreate()
|
|
599
|
+
pin_monitor.on_unpin(self)
|
|
600
|
+
|
|
601
|
+
if self.meta.pin_count <= 0 and self.meta.ref_count <= 0:
|
|
602
|
+
if self.parent_allocator is None:
|
|
603
|
+
logger.error(
|
|
604
|
+
"Parent allocator is None when trying to free MemoryObj."
|
|
605
|
+
"This could cause memory leak"
|
|
606
|
+
)
|
|
607
|
+
else:
|
|
608
|
+
self.parent_allocator.free(self)
|
|
609
|
+
|
|
610
|
+
if self.meta.pin_count < 0:
|
|
611
|
+
logger.warning(
|
|
612
|
+
f"Pin count of MemoryObj {self.meta.address}"
|
|
613
|
+
f"is negative: {self.meta.pin_count}."
|
|
614
|
+
"Double unpin occurred somewhere."
|
|
615
|
+
"Setting pin count back to 0 as a hack but please find the bug."
|
|
616
|
+
)
|
|
617
|
+
self.meta.pin_count = 0
|
|
618
|
+
return True
|
|
619
|
+
|
|
620
|
+
@property
|
|
621
|
+
def metadata(self) -> MemoryObjMetadata:
|
|
622
|
+
with self.lock:
|
|
623
|
+
return self.meta
|
|
624
|
+
|
|
625
|
+
@property
|
|
626
|
+
def tensor(self) -> Optional[torch.Tensor]:
|
|
627
|
+
if not self.valid:
|
|
628
|
+
logger.warning("Trying to access an invalidated MemoryObj")
|
|
629
|
+
return None
|
|
630
|
+
assert self.meta.dtype is not None
|
|
631
|
+
# TODO(Jiayi): consider caching the `get_size()`
|
|
632
|
+
return (
|
|
633
|
+
self.raw_data[: self.get_size()].view(self.meta.dtype).view(self.meta.shape)
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def byte_array(self) -> memoryview:
|
|
638
|
+
# TODO: consider using one of the alternatives
|
|
639
|
+
|
|
640
|
+
# Alternative 1:
|
|
641
|
+
# # PyTorch tensors support buffer protocol directly for CPU tensors
|
|
642
|
+
# return memoryview(self.raw_data)
|
|
643
|
+
|
|
644
|
+
# Alternative 2:
|
|
645
|
+
# assert self.raw_data.device.type == 'cpu',
|
|
646
|
+
# "byte_array only works with CPU tensors"
|
|
647
|
+
# return memoryview(self.raw_data.contiguous().numpy())
|
|
648
|
+
|
|
649
|
+
# Use logical size (get_size) rather than raw_data physical size.
|
|
650
|
+
# The raw_data buffer may include alignment padding (e.g. from
|
|
651
|
+
# batched_allocate) that must not be exposed to callers such as
|
|
652
|
+
# remote-backend put/get which rely on byte_array length matching
|
|
653
|
+
# the metadata length.
|
|
654
|
+
num_bytes = self.get_size()
|
|
655
|
+
ptr = self.raw_data.data_ptr()
|
|
656
|
+
ubyte_ptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte))
|
|
657
|
+
byte_array = (ctypes.c_ubyte * num_bytes).from_address(
|
|
658
|
+
ctypes.addressof(ubyte_ptr.contents)
|
|
659
|
+
)
|
|
660
|
+
return memoryview(byte_array)
|
|
661
|
+
|
|
662
|
+
@property
|
|
663
|
+
def data_ptr(self) -> int:
|
|
664
|
+
return self.raw_data.data_ptr()
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def is_pinned(self) -> bool:
|
|
668
|
+
return self.metadata.pin_count > 0
|
|
669
|
+
|
|
670
|
+
@property
|
|
671
|
+
def can_evict(self) -> bool:
|
|
672
|
+
"""
|
|
673
|
+
Check whether the memory obj can be evicted.
|
|
674
|
+
A memory obj can be evicted if it is not pinned and ref_count=1.
|
|
675
|
+
"""
|
|
676
|
+
return not self.is_pinned and self.get_ref_count() == 1
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def raw_tensor(self) -> Optional[torch.Tensor]:
|
|
680
|
+
if not self.valid:
|
|
681
|
+
logger.warning("Trying to access an invalidated MemoryObj")
|
|
682
|
+
return None
|
|
683
|
+
return self.raw_data
|
|
684
|
+
|
|
685
|
+
def get_tensor(self, index: int) -> Optional[torch.Tensor]:
|
|
686
|
+
if not self.valid:
|
|
687
|
+
logger.warning("Trying to access an invalidated MemoryObj")
|
|
688
|
+
return None
|
|
689
|
+
assert self.meta.shapes is not None
|
|
690
|
+
assert self.meta.dtypes is not None
|
|
691
|
+
begin = self.group_prefix_sum[index]
|
|
692
|
+
end = self.group_prefix_sum[index + 1]
|
|
693
|
+
return (
|
|
694
|
+
self.raw_data[begin:end]
|
|
695
|
+
.view(self.meta.dtypes[index])
|
|
696
|
+
.view(self.meta.shapes[index])
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
def parent(self) -> Optional["MemoryAllocatorInterface"]:
|
|
700
|
+
return self.parent_allocator
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class BytesBufferMemoryObj(MemoryObj):
|
|
704
|
+
"""
|
|
705
|
+
Wraps a raw flat tensor with some metadata
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
def __init__(self, raw_bytes: bytes, metadata: Optional[MemoryObjMetadata] = None):
|
|
709
|
+
self.raw_data = raw_bytes
|
|
710
|
+
if metadata is None:
|
|
711
|
+
bytes_shape = torch.Size([len(self.raw_data), 0, 0, 0])
|
|
712
|
+
metadata = MemoryObjMetadata(
|
|
713
|
+
shape=bytes_shape,
|
|
714
|
+
dtype=None,
|
|
715
|
+
address=0,
|
|
716
|
+
phy_size=0,
|
|
717
|
+
ref_count=1,
|
|
718
|
+
pin_count=0,
|
|
719
|
+
fmt=MemoryFormat.BINARY_BUFFER,
|
|
720
|
+
)
|
|
721
|
+
super().__init__(metadata)
|
|
722
|
+
self.valid = True
|
|
723
|
+
|
|
724
|
+
def invalidate(self):
|
|
725
|
+
self.valid = False
|
|
726
|
+
|
|
727
|
+
def is_valid(self):
|
|
728
|
+
return self.valid
|
|
729
|
+
|
|
730
|
+
def get_size(self) -> int:
|
|
731
|
+
return len(self.raw_data)
|
|
732
|
+
|
|
733
|
+
def get_shape(self) -> torch.Size:
|
|
734
|
+
return torch.Size([len(self.raw_data), 0, 0, 0])
|
|
735
|
+
|
|
736
|
+
def get_dtype(self) -> Optional[torch.dtype]:
|
|
737
|
+
return None
|
|
738
|
+
|
|
739
|
+
def get_shapes(self) -> list[torch.Size]:
|
|
740
|
+
return [self.get_shape()]
|
|
741
|
+
|
|
742
|
+
def get_dtypes(self) -> list[torch.dtype]:
|
|
743
|
+
return []
|
|
744
|
+
|
|
745
|
+
def get_memory_format(self) -> MemoryFormat:
|
|
746
|
+
return self.metadata.fmt
|
|
747
|
+
|
|
748
|
+
def get_physical_size(self) -> int:
|
|
749
|
+
return self.metadata.phy_size
|
|
750
|
+
|
|
751
|
+
def pin(self) -> bool:
|
|
752
|
+
self.metadata.pin_count += 1
|
|
753
|
+
return True
|
|
754
|
+
|
|
755
|
+
def unpin(self) -> bool:
|
|
756
|
+
self.metadata.pin_count -= 1
|
|
757
|
+
if self.metadata.pin_count < 0:
|
|
758
|
+
logger.warning(
|
|
759
|
+
f"Pin count of MemoryObj {self.meta.address}"
|
|
760
|
+
f"is negative: {self.meta.pin_count}."
|
|
761
|
+
"Double unpin occurred somewhere."
|
|
762
|
+
"Setting pin count back to 0 as a hack but please find the bug."
|
|
763
|
+
)
|
|
764
|
+
self.metadata.pin_count = 0
|
|
765
|
+
return True
|
|
766
|
+
|
|
767
|
+
def ref_count_up(self):
|
|
768
|
+
pass
|
|
769
|
+
|
|
770
|
+
def ref_count_down(self):
|
|
771
|
+
pass
|
|
772
|
+
|
|
773
|
+
def get_ref_count(self) -> int:
|
|
774
|
+
return 1
|
|
775
|
+
|
|
776
|
+
def get_num_tokens(self) -> int:
|
|
777
|
+
# TODO(Jiayi): record the number of tokens somehow
|
|
778
|
+
return 1
|
|
779
|
+
|
|
780
|
+
@property
|
|
781
|
+
def metadata(self) -> MemoryObjMetadata:
|
|
782
|
+
return self.meta
|
|
783
|
+
|
|
784
|
+
@property
|
|
785
|
+
def tensor(self) -> Optional[torch.Tensor]:
|
|
786
|
+
if not self.valid:
|
|
787
|
+
logger.warning("Trying to access an invalidated MemoryObj")
|
|
788
|
+
return None
|
|
789
|
+
return None
|
|
790
|
+
|
|
791
|
+
@property
|
|
792
|
+
def byte_array(self) -> bytes:
|
|
793
|
+
return self.raw_data
|
|
794
|
+
|
|
795
|
+
@property
|
|
796
|
+
def data_ptr(self) -> int:
|
|
797
|
+
mv = memoryview(self.raw_data)
|
|
798
|
+
addr = ctypes.addressof(ctypes.c_char.from_buffer(mv))
|
|
799
|
+
return addr
|
|
800
|
+
|
|
801
|
+
@property
|
|
802
|
+
def is_pinned(self) -> bool:
|
|
803
|
+
return self.metadata.pin_count > 0
|
|
804
|
+
|
|
805
|
+
@property
|
|
806
|
+
def can_evict(self) -> bool:
|
|
807
|
+
"""
|
|
808
|
+
Check whether the memory obj can be evicted.
|
|
809
|
+
A buffer memory obj can be evicted if it is not pinned.
|
|
810
|
+
"""
|
|
811
|
+
return not self.is_pinned
|
|
812
|
+
|
|
813
|
+
@property
|
|
814
|
+
def raw_tensor(self) -> Optional[torch.Tensor]:
|
|
815
|
+
if not self.valid:
|
|
816
|
+
logger.warning("Trying to access an invalidated MemoryObj")
|
|
817
|
+
return None
|
|
818
|
+
return None
|
|
819
|
+
|
|
820
|
+
def get_tensor(self, index: int) -> Optional[torch.Tensor]:
|
|
821
|
+
return None
|
|
822
|
+
|
|
823
|
+
def parent(self) -> Optional["MemoryAllocatorInterface"]:
|
|
824
|
+
# NOTE: BytesBufferMemoryObj may not be allocated by any allocator,
|
|
825
|
+
# so just return None here
|
|
826
|
+
return None
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
class MemoryAllocatorInterface(metaclass=abc.ABCMeta):
|
|
830
|
+
@abc.abstractmethod
|
|
831
|
+
def allocate(
|
|
832
|
+
self,
|
|
833
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
834
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
835
|
+
fmt: MemoryFormat = MemoryFormat.UNDEFINED,
|
|
836
|
+
allocator_type: Optional[str] = None,
|
|
837
|
+
) -> Optional[MemoryObj]:
|
|
838
|
+
"""
|
|
839
|
+
Allocates the memory to hold a tensor of the given shape.
|
|
840
|
+
|
|
841
|
+
:param torch.Size shapes: The shape of the tensor to allocate.
|
|
842
|
+
:param torch.dtype dtypes: The dtype of the tensor to allocate.
|
|
843
|
+
:param MemoryFormat fmt: The format of the memory to allocate.
|
|
844
|
+
|
|
845
|
+
:return: A MemoryObj wrapping the allocated memory. Returns
|
|
846
|
+
None if the allocation failed.
|
|
847
|
+
|
|
848
|
+
:rtype: Optional[MemoryObj]
|
|
849
|
+
"""
|
|
850
|
+
raise NotImplementedError
|
|
851
|
+
|
|
852
|
+
@abc.abstractmethod
|
|
853
|
+
def batched_allocate(
|
|
854
|
+
self,
|
|
855
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
856
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
857
|
+
batch_size: int,
|
|
858
|
+
fmt: MemoryFormat = MemoryFormat.UNDEFINED,
|
|
859
|
+
allocator_type: Optional[str] = None,
|
|
860
|
+
) -> Optional[List[MemoryObj]]:
|
|
861
|
+
"""
|
|
862
|
+
Batched allocate the memory to hold a tensor of the given shape.
|
|
863
|
+
|
|
864
|
+
:param torch.Size shapes: The shape of the tensor to allocate.
|
|
865
|
+
:param torch.dtype dtypes: The dtype of the tensor to allocate.
|
|
866
|
+
:param int batch_size: The number of tensors to allocate.
|
|
867
|
+
:param MemoryFormat fmt: The format of the memory to allocate.
|
|
868
|
+
|
|
869
|
+
:return: A list of MemoryObjs wrapping the allocated memory.
|
|
870
|
+
Returns None if the allocation failed.
|
|
871
|
+
|
|
872
|
+
:rtype: Optional[List[MemoryObj]]
|
|
873
|
+
"""
|
|
874
|
+
raise NotImplementedError
|
|
875
|
+
|
|
876
|
+
@abc.abstractmethod
|
|
877
|
+
def free(
|
|
878
|
+
self,
|
|
879
|
+
memory_obj: MemoryObj,
|
|
880
|
+
allocator_type: Optional[str] = None,
|
|
881
|
+
):
|
|
882
|
+
"""
|
|
883
|
+
Frees the memory allocated for the given MemoryObj.
|
|
884
|
+
Note that this function shouldn't be explicitly called.
|
|
885
|
+
Instead, use `ref_count_down` to decrease ref count.
|
|
886
|
+
|
|
887
|
+
:param MemoryObj memory_obj: The MemoryObj to free.
|
|
888
|
+
"""
|
|
889
|
+
raise NotImplementedError
|
|
890
|
+
|
|
891
|
+
@abc.abstractmethod
|
|
892
|
+
def batched_free(
|
|
893
|
+
self,
|
|
894
|
+
memory_objs: List[MemoryObj],
|
|
895
|
+
allocator_type: Optional[str] = None,
|
|
896
|
+
update_stats: bool = True,
|
|
897
|
+
):
|
|
898
|
+
"""
|
|
899
|
+
Frees the memory allocated for the given list of MemoryObjs.
|
|
900
|
+
|
|
901
|
+
:param List[MemoryObj] memory_objs: The list of MemoryObjs
|
|
902
|
+
to free.
|
|
903
|
+
"""
|
|
904
|
+
raise NotImplementedError
|
|
905
|
+
|
|
906
|
+
def close(self):
|
|
907
|
+
"""
|
|
908
|
+
Closes the memory allocator.
|
|
909
|
+
This is called when the LMCacheEngine is closed.
|
|
910
|
+
"""
|
|
911
|
+
return
|
|
912
|
+
|
|
913
|
+
def memcheck(self) -> bool:
|
|
914
|
+
"""
|
|
915
|
+
Checks the memory allocator for consistency.
|
|
916
|
+
|
|
917
|
+
Returns:
|
|
918
|
+
True if everything is fine otherwise False
|
|
919
|
+
"""
|
|
920
|
+
return True
|
|
921
|
+
|
|
922
|
+
# TODO(chunxiaozheng): remove if after all params replaced by shapes/dtypes
|
|
923
|
+
def _adapt_shapes_and_dtypes(
|
|
924
|
+
self,
|
|
925
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
926
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
927
|
+
) -> Tuple[list[torch.Size], list[torch.dtype]]:
|
|
928
|
+
if isinstance(shapes, torch.Size):
|
|
929
|
+
shapes = [shapes]
|
|
930
|
+
|
|
931
|
+
if isinstance(dtypes, torch.dtype):
|
|
932
|
+
dtypes = [dtypes]
|
|
933
|
+
|
|
934
|
+
assert len(shapes) == len(dtypes), (
|
|
935
|
+
f"shapes and dtypes must have the same length, "
|
|
936
|
+
f"got {len(shapes)} and {len(dtypes)}, "
|
|
937
|
+
f"shapes: {shapes}, dtypes: {dtypes}"
|
|
938
|
+
)
|
|
939
|
+
return shapes, dtypes
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
class AddressManager:
|
|
943
|
+
"""
|
|
944
|
+
Manages a virtual address space starting from 0 for memory allocation.
|
|
945
|
+
|
|
946
|
+
Key interfaces:
|
|
947
|
+
- allocate(size): Allocate a block of memory of the given size. The starting
|
|
948
|
+
address and the actual allocated size will be aligned.
|
|
949
|
+
|
|
950
|
+
- free(address, size): Free a previously allocated region. Note that if the
|
|
951
|
+
region is not "allocated" before, it may have internal errors.
|
|
952
|
+
|
|
953
|
+
- sbrk(size): Expand the virtual address space by the given size. The size
|
|
954
|
+
will be aligned internally.
|
|
955
|
+
|
|
956
|
+
Core assumptions:
|
|
957
|
+
- The allocated size should be aligned with ALIGN_BYTES.
|
|
958
|
+
"""
|
|
959
|
+
|
|
960
|
+
ALIGN_BYTES = 4096
|
|
961
|
+
|
|
962
|
+
def __init__(self, size: int, align_bytes: int = ALIGN_BYTES):
|
|
963
|
+
"""
|
|
964
|
+
Initializes the AddressManager with a given size.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
size: The initial size of the virtual address space.
|
|
968
|
+
align_bytes: The alignment requirement for allocations.
|
|
969
|
+
"""
|
|
970
|
+
self._size = size
|
|
971
|
+
self._align = align_bytes
|
|
972
|
+
|
|
973
|
+
# Current implementation: explicit list
|
|
974
|
+
self._explicit_list: SortedList[FreeBlock] = SortedList(key=lambda x: x.start)
|
|
975
|
+
self._explicit_list.add(FreeBlock(start=0, size=size))
|
|
976
|
+
|
|
977
|
+
# thread safe lock
|
|
978
|
+
self._lock = threading.Lock()
|
|
979
|
+
|
|
980
|
+
# For debugging purposes
|
|
981
|
+
self.total_allocated_size = 0
|
|
982
|
+
|
|
983
|
+
def compute_aligned_size(self, raw_size: int) -> int:
|
|
984
|
+
"""
|
|
985
|
+
Helper function to compute the aligned size for a given raw size.
|
|
986
|
+
|
|
987
|
+
Args:
|
|
988
|
+
raw_size: The raw size to be aligned.
|
|
989
|
+
|
|
990
|
+
Returns:
|
|
991
|
+
The aligned size.
|
|
992
|
+
"""
|
|
993
|
+
return (raw_size + self._align - 1) & ~(self._align - 1)
|
|
994
|
+
|
|
995
|
+
def _can_merge_with_prev(
|
|
996
|
+
self, curr_block: FreeBlock, prev_block: FreeBlock
|
|
997
|
+
) -> bool:
|
|
998
|
+
"""Hook: Check if curr_block can merge with prev_block."""
|
|
999
|
+
return prev_block.can_be_coalesced(curr_block)
|
|
1000
|
+
|
|
1001
|
+
def _can_merge_with_succ(
|
|
1002
|
+
self, curr_block: FreeBlock, succ_block: FreeBlock
|
|
1003
|
+
) -> bool:
|
|
1004
|
+
"""Hook: Check if curr_block can merge with succ_block."""
|
|
1005
|
+
return curr_block.can_be_coalesced(succ_block)
|
|
1006
|
+
|
|
1007
|
+
@_lmcache_nvtx_annotate
|
|
1008
|
+
def _coalesce(
|
|
1009
|
+
self,
|
|
1010
|
+
curr_block: FreeBlock,
|
|
1011
|
+
prev_block: Optional[FreeBlock],
|
|
1012
|
+
succ_block: Optional[FreeBlock],
|
|
1013
|
+
):
|
|
1014
|
+
"""
|
|
1015
|
+
Coalesces the current block with the previous and/or successor block.
|
|
1016
|
+
This assumes the curr_block is NOT in self._explicit_list
|
|
1017
|
+
|
|
1018
|
+
Returns True if the current block was coalesced, otherwise False.
|
|
1019
|
+
"""
|
|
1020
|
+
merge_prev = prev_block is not None and self._can_merge_with_prev(
|
|
1021
|
+
curr_block, prev_block
|
|
1022
|
+
)
|
|
1023
|
+
merge_succ = succ_block is not None and self._can_merge_with_succ(
|
|
1024
|
+
curr_block, succ_block
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
if merge_prev and merge_succ:
|
|
1028
|
+
prev_block.size += curr_block.size + succ_block.size # type: ignore
|
|
1029
|
+
self._explicit_list.remove(succ_block)
|
|
1030
|
+
elif merge_prev:
|
|
1031
|
+
prev_block.size += curr_block.size # type: ignore
|
|
1032
|
+
elif merge_succ:
|
|
1033
|
+
# NOTE: logically, this won't change the order of the succ_block,
|
|
1034
|
+
# so we don't need to do a "remove" and "reinsert" here
|
|
1035
|
+
self._explicit_list.remove(succ_block)
|
|
1036
|
+
succ_block.start -= curr_block.size # type: ignore
|
|
1037
|
+
succ_block.size += curr_block.size # type: ignore
|
|
1038
|
+
self._explicit_list.add(succ_block)
|
|
1039
|
+
|
|
1040
|
+
return merge_prev or merge_succ
|
|
1041
|
+
|
|
1042
|
+
@_lmcache_nvtx_annotate
|
|
1043
|
+
@synchronized("_lock")
|
|
1044
|
+
def allocate(self, size: int) -> tuple[int, int]:
|
|
1045
|
+
"""
|
|
1046
|
+
Allocate a block of memory from the virtual address space of a given
|
|
1047
|
+
size. The actual allocated size could be larger than the requested size
|
|
1048
|
+
in order to satisfy alignment requirements.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
size: The requested size of the memory block. Should be greater
|
|
1052
|
+
than 0.
|
|
1053
|
+
|
|
1054
|
+
Returns:
|
|
1055
|
+
A tuple (address, allocated_size) where address is the starting
|
|
1056
|
+
address of the allocated block and allocated_size is the actual
|
|
1057
|
+
size of the allocated block.
|
|
1058
|
+
|
|
1059
|
+
Raises:
|
|
1060
|
+
RuntimeError: If no memory is available to allocate.
|
|
1061
|
+
"""
|
|
1062
|
+
aligned_size = self.compute_aligned_size(size)
|
|
1063
|
+
for block in self._explicit_list:
|
|
1064
|
+
if block.size >= aligned_size:
|
|
1065
|
+
break
|
|
1066
|
+
else:
|
|
1067
|
+
logger.warning(
|
|
1068
|
+
"Failed to allocate memory block of size %d "
|
|
1069
|
+
"because no memory is available",
|
|
1070
|
+
size,
|
|
1071
|
+
)
|
|
1072
|
+
raise RuntimeError(
|
|
1073
|
+
f"Failed to allocate memory block of size {size} "
|
|
1074
|
+
"because no memory is available"
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
self._explicit_list.remove(block)
|
|
1078
|
+
if block.size > aligned_size:
|
|
1079
|
+
self._explicit_list.add(
|
|
1080
|
+
FreeBlock(
|
|
1081
|
+
start=block.start + aligned_size,
|
|
1082
|
+
size=block.size - aligned_size,
|
|
1083
|
+
)
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
# For debug
|
|
1087
|
+
self.total_allocated_size += aligned_size
|
|
1088
|
+
|
|
1089
|
+
return block.start, aligned_size
|
|
1090
|
+
|
|
1091
|
+
@_lmcache_nvtx_annotate
|
|
1092
|
+
@synchronized("_lock")
|
|
1093
|
+
def batched_allocate(self, size: int, batch_size: int) -> list[tuple[int, int]]:
|
|
1094
|
+
"""
|
|
1095
|
+
Allocate blocks of memory from the virtual address space of a given
|
|
1096
|
+
size and batch size. The actual allocated size could be larger than
|
|
1097
|
+
the requested size in order to satisfy alignment requirements.
|
|
1098
|
+
|
|
1099
|
+
Args:
|
|
1100
|
+
size: The requested size of the memory block. Should be greater
|
|
1101
|
+
than 0.
|
|
1102
|
+
batch_size: The number of memory blocks to allocate.
|
|
1103
|
+
|
|
1104
|
+
Returns:
|
|
1105
|
+
A list of tuple (address, allocated_size) where address is the starting
|
|
1106
|
+
address of the allocated block and allocated_size is the actual size of
|
|
1107
|
+
the allocated block.
|
|
1108
|
+
Note: the length of the return list is the same as the batch_size.
|
|
1109
|
+
|
|
1110
|
+
Raises:
|
|
1111
|
+
RuntimeError: If no memory is available to allocate.
|
|
1112
|
+
"""
|
|
1113
|
+
aligned_size = self.compute_aligned_size(size)
|
|
1114
|
+
remaining = batch_size
|
|
1115
|
+
allocate_result: list[tuple[int, int]] = []
|
|
1116
|
+
|
|
1117
|
+
blocks_to_remove: list[FreeBlock] = []
|
|
1118
|
+
blocks_to_add: list[FreeBlock] = []
|
|
1119
|
+
|
|
1120
|
+
for block in self._explicit_list:
|
|
1121
|
+
if remaining <= 0:
|
|
1122
|
+
break
|
|
1123
|
+
if block.size < aligned_size:
|
|
1124
|
+
continue
|
|
1125
|
+
|
|
1126
|
+
# Greedily carve out as many aligned_size chunks as possible
|
|
1127
|
+
num_from_block = min(remaining, block.size // aligned_size)
|
|
1128
|
+
start = block.start
|
|
1129
|
+
for i in range(num_from_block):
|
|
1130
|
+
allocate_result.append((start + i * aligned_size, aligned_size))
|
|
1131
|
+
remaining -= num_from_block
|
|
1132
|
+
|
|
1133
|
+
# Mark the original block for removal
|
|
1134
|
+
blocks_to_remove.append(block)
|
|
1135
|
+
|
|
1136
|
+
# Keep the remaining tail as a new free block if any space is left
|
|
1137
|
+
used = num_from_block * aligned_size
|
|
1138
|
+
if block.size > used:
|
|
1139
|
+
blocks_to_add.append(
|
|
1140
|
+
FreeBlock(start=block.start + used, size=block.size - used)
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
if remaining > 0:
|
|
1144
|
+
# Not enough memory; free list is untouched, no rollback needed
|
|
1145
|
+
logger.warning(
|
|
1146
|
+
"Failed to batched allocate %d memory blocks of size %d "
|
|
1147
|
+
"because no enough memory is available (short by %d blocks)",
|
|
1148
|
+
batch_size,
|
|
1149
|
+
size,
|
|
1150
|
+
remaining,
|
|
1151
|
+
)
|
|
1152
|
+
raise RuntimeError(
|
|
1153
|
+
f"Failed to batched allocate {batch_size} memory blocks "
|
|
1154
|
+
f"of size {size} because no enough memory is available"
|
|
1155
|
+
)
|
|
1156
|
+
if len(allocate_result) != batch_size:
|
|
1157
|
+
# The length of allocate_result is not equal to batch_size;
|
|
1158
|
+
# free list is untouched, no rollback needed
|
|
1159
|
+
logger.warning(
|
|
1160
|
+
"Failed to batched allocate %d memory blocks of size %d "
|
|
1161
|
+
"because the length of allocate_result %d is not equal to batch_size",
|
|
1162
|
+
batch_size,
|
|
1163
|
+
size,
|
|
1164
|
+
len(allocate_result),
|
|
1165
|
+
)
|
|
1166
|
+
raise RuntimeError(
|
|
1167
|
+
f"Failed to batched allocate {batch_size} memory blocks "
|
|
1168
|
+
f"of size {size} because the length of allocate_result "
|
|
1169
|
+
f"{len(allocate_result)} is not equal to batch_size"
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
# Allocation succeeded; batch-update the free list
|
|
1173
|
+
for block in blocks_to_remove:
|
|
1174
|
+
self._explicit_list.remove(block)
|
|
1175
|
+
for block in blocks_to_add:
|
|
1176
|
+
self._explicit_list.add(block)
|
|
1177
|
+
|
|
1178
|
+
# Update debug statistics
|
|
1179
|
+
total_allocated = aligned_size * batch_size
|
|
1180
|
+
self.total_allocated_size += total_allocated
|
|
1181
|
+
|
|
1182
|
+
return allocate_result
|
|
1183
|
+
|
|
1184
|
+
@_lmcache_nvtx_annotate
|
|
1185
|
+
@synchronized("_lock")
|
|
1186
|
+
def free(self, address: int, size: int):
|
|
1187
|
+
"""
|
|
1188
|
+
Free a previously allocated block of memory.
|
|
1189
|
+
|
|
1190
|
+
Args:
|
|
1191
|
+
address: The starting address of the block to free.
|
|
1192
|
+
size: The size of the block to free. Should be greater than 0.
|
|
1193
|
+
"""
|
|
1194
|
+
new_free_block = FreeBlock(start=address, size=size)
|
|
1195
|
+
index = self._explicit_list.bisect_left(new_free_block)
|
|
1196
|
+
prev_block = self._explicit_list[index - 1] if index > 0 else None
|
|
1197
|
+
succ_block = (
|
|
1198
|
+
self._explicit_list[index] if index < len(self._explicit_list) else None
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
coalesced = self._coalesce(new_free_block, prev_block, succ_block)
|
|
1202
|
+
if not coalesced:
|
|
1203
|
+
self._explicit_list.add(new_free_block)
|
|
1204
|
+
|
|
1205
|
+
# For debug
|
|
1206
|
+
self.total_allocated_size -= size
|
|
1207
|
+
|
|
1208
|
+
@synchronized("_lock")
|
|
1209
|
+
def sbrk(self, size: int):
|
|
1210
|
+
"""
|
|
1211
|
+
Expand the virtual address space by a given size.
|
|
1212
|
+
|
|
1213
|
+
Args:
|
|
1214
|
+
size: The size to expand the address space. Will be aligned internally
|
|
1215
|
+
with the ALIGN_BYTES
|
|
1216
|
+
"""
|
|
1217
|
+
size = self.compute_aligned_size(size)
|
|
1218
|
+
new_block = FreeBlock(start=self._size, size=size)
|
|
1219
|
+
prev_block = self._explicit_list[-1] if len(self._explicit_list) > 0 else None
|
|
1220
|
+
succ_block = None
|
|
1221
|
+
coalesced = self._coalesce(new_block, prev_block, succ_block)
|
|
1222
|
+
if not coalesced:
|
|
1223
|
+
self._explicit_list.add(new_block)
|
|
1224
|
+
|
|
1225
|
+
self._size += size
|
|
1226
|
+
|
|
1227
|
+
def get_heap_size(self) -> int:
|
|
1228
|
+
"""
|
|
1229
|
+
Get the total size of the address space.
|
|
1230
|
+
|
|
1231
|
+
Returns:
|
|
1232
|
+
The total size in bytes.
|
|
1233
|
+
"""
|
|
1234
|
+
return self._size
|
|
1235
|
+
|
|
1236
|
+
def get_free_size(self) -> int:
|
|
1237
|
+
"""
|
|
1238
|
+
Get the total free size in the address space.
|
|
1239
|
+
|
|
1240
|
+
Returns:
|
|
1241
|
+
The total free size in bytes.
|
|
1242
|
+
"""
|
|
1243
|
+
return self._size - self.total_allocated_size
|
|
1244
|
+
|
|
1245
|
+
def check_consistency(self) -> bool:
|
|
1246
|
+
"""
|
|
1247
|
+
Check if the address manager is consistent.
|
|
1248
|
+
|
|
1249
|
+
Returns:
|
|
1250
|
+
True if consistent, False otherwise.
|
|
1251
|
+
"""
|
|
1252
|
+
# Check if free blocks are properly coalesced
|
|
1253
|
+
for prev, succ in zip(
|
|
1254
|
+
self._explicit_list[:-1], self._explicit_list[1:], strict=False
|
|
1255
|
+
):
|
|
1256
|
+
if prev.can_be_coalesced(succ):
|
|
1257
|
+
return False
|
|
1258
|
+
|
|
1259
|
+
# Check if total size matches
|
|
1260
|
+
total_free_size = sum(block.size for block in self._explicit_list)
|
|
1261
|
+
if total_free_size + self.total_allocated_size != self._size:
|
|
1262
|
+
return False
|
|
1263
|
+
|
|
1264
|
+
return True
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
class TensorMemoryAllocator(MemoryAllocatorInterface):
|
|
1268
|
+
"""
|
|
1269
|
+
Implements a "explicit list" memory allocator.
|
|
1270
|
+
Uses AddressManager for address space management.
|
|
1271
|
+
"""
|
|
1272
|
+
|
|
1273
|
+
def __init__(
|
|
1274
|
+
self,
|
|
1275
|
+
tensor: torch.Tensor,
|
|
1276
|
+
align_bytes: int = AddressManager.ALIGN_BYTES,
|
|
1277
|
+
init_address_space: int | None = None,
|
|
1278
|
+
):
|
|
1279
|
+
"""
|
|
1280
|
+
Args:
|
|
1281
|
+
tensor: The pre-allocated flat tensor to use as the memory pool.
|
|
1282
|
+
align_bytes: The alignment requirement for allocations.
|
|
1283
|
+
init_address_space: Initial size of the address space. If None,
|
|
1284
|
+
use the size of the provided tensor.
|
|
1285
|
+
|
|
1286
|
+
Note:
|
|
1287
|
+
The `init_address_space` is used for lazy memory allocation.
|
|
1288
|
+
We probably want to have a better way to make sure that the
|
|
1289
|
+
LazyMemoryAllocator can be decoupled from TensorMemoryAllocator.
|
|
1290
|
+
"""
|
|
1291
|
+
self.buffer = tensor.view(torch.uint8).flatten()
|
|
1292
|
+
|
|
1293
|
+
# Use AddressManager for address space management
|
|
1294
|
+
self.address_manager = AddressManager(
|
|
1295
|
+
self.buffer.numel() if init_address_space is None else init_address_space,
|
|
1296
|
+
align_bytes,
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
# For debugging purposes
|
|
1300
|
+
self.num_active_allocations = 0
|
|
1301
|
+
|
|
1302
|
+
self.stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
1303
|
+
|
|
1304
|
+
@property
|
|
1305
|
+
def total_allocated_size(self) -> int:
|
|
1306
|
+
return self.address_manager.total_allocated_size
|
|
1307
|
+
|
|
1308
|
+
@_lmcache_nvtx_annotate
|
|
1309
|
+
def allocate(
|
|
1310
|
+
self,
|
|
1311
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1312
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1313
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1314
|
+
allocator_type: Optional[str] = None,
|
|
1315
|
+
) -> Optional[TensorMemoryObj]:
|
|
1316
|
+
shapes, dtypes = self._adapt_shapes_and_dtypes(shapes, dtypes)
|
|
1317
|
+
|
|
1318
|
+
# Calculate the size of the tensor
|
|
1319
|
+
raw_size = get_size_bytes(shapes, dtypes)
|
|
1320
|
+
|
|
1321
|
+
# Allocate from address manager
|
|
1322
|
+
try:
|
|
1323
|
+
block_start, aligned_size = self.address_manager.allocate(raw_size)
|
|
1324
|
+
except RuntimeError:
|
|
1325
|
+
# No block found
|
|
1326
|
+
return None
|
|
1327
|
+
|
|
1328
|
+
# For debug
|
|
1329
|
+
self.num_active_allocations += 1
|
|
1330
|
+
|
|
1331
|
+
# Update stats
|
|
1332
|
+
self.stats_monitor.update_local_cache_usage(
|
|
1333
|
+
self.address_manager.total_allocated_size
|
|
1334
|
+
)
|
|
1335
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1336
|
+
|
|
1337
|
+
# Allocate the block
|
|
1338
|
+
raw_data = self._get_buffer_slice(block_start, raw_size)
|
|
1339
|
+
return TensorMemoryObj(
|
|
1340
|
+
raw_data=raw_data,
|
|
1341
|
+
metadata=MemoryObjMetadata(
|
|
1342
|
+
shapes[0],
|
|
1343
|
+
dtypes[0],
|
|
1344
|
+
block_start,
|
|
1345
|
+
aligned_size,
|
|
1346
|
+
1,
|
|
1347
|
+
0,
|
|
1348
|
+
fmt,
|
|
1349
|
+
shapes=shapes,
|
|
1350
|
+
dtypes=dtypes,
|
|
1351
|
+
),
|
|
1352
|
+
parent_allocator=self,
|
|
1353
|
+
)
|
|
1354
|
+
|
|
1355
|
+
def _get_buffer_slice(self, start: int, size: int) -> torch.Tensor:
|
|
1356
|
+
"""Hook: Get buffer slice. Override for custom buffer access."""
|
|
1357
|
+
return self.buffer[start : start + size]
|
|
1358
|
+
|
|
1359
|
+
@_lmcache_nvtx_annotate
|
|
1360
|
+
def batched_allocate(
|
|
1361
|
+
self,
|
|
1362
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1363
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1364
|
+
batch_size: int,
|
|
1365
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1366
|
+
allocator_type: Optional[str] = None,
|
|
1367
|
+
) -> Optional[List[TensorMemoryObj]]:
|
|
1368
|
+
"""
|
|
1369
|
+
Batched allocate tensor memory objs with equal sizes.
|
|
1370
|
+
"""
|
|
1371
|
+
shapes, dtypes = self._adapt_shapes_and_dtypes(shapes, dtypes)
|
|
1372
|
+
|
|
1373
|
+
# Calculate the size of the tensor
|
|
1374
|
+
unit_raw_size = get_size_bytes(shapes, dtypes)
|
|
1375
|
+
unit_aligned_size = self.address_manager.compute_aligned_size(unit_raw_size)
|
|
1376
|
+
|
|
1377
|
+
try:
|
|
1378
|
+
alloc_results = self.address_manager.batched_allocate(
|
|
1379
|
+
unit_aligned_size, batch_size
|
|
1380
|
+
)
|
|
1381
|
+
except RuntimeError:
|
|
1382
|
+
return None
|
|
1383
|
+
addresses = [addr for addr, _ in alloc_results]
|
|
1384
|
+
raw_datas = [
|
|
1385
|
+
self._get_buffer_slice(addr, unit_aligned_size) for addr in addresses
|
|
1386
|
+
]
|
|
1387
|
+
|
|
1388
|
+
# For debug
|
|
1389
|
+
self.num_active_allocations += batch_size
|
|
1390
|
+
|
|
1391
|
+
# Update stats
|
|
1392
|
+
self.stats_monitor.update_local_cache_usage(
|
|
1393
|
+
self.address_manager.total_allocated_size
|
|
1394
|
+
)
|
|
1395
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1396
|
+
|
|
1397
|
+
tensor_mem_objs = []
|
|
1398
|
+
for raw_data, address in zip(raw_datas, addresses, strict=True):
|
|
1399
|
+
tensor_mem_objs.append(
|
|
1400
|
+
TensorMemoryObj(
|
|
1401
|
+
raw_data=raw_data,
|
|
1402
|
+
metadata=MemoryObjMetadata(
|
|
1403
|
+
shapes[0],
|
|
1404
|
+
dtypes[0],
|
|
1405
|
+
address,
|
|
1406
|
+
unit_aligned_size,
|
|
1407
|
+
1,
|
|
1408
|
+
0,
|
|
1409
|
+
fmt,
|
|
1410
|
+
shapes=shapes,
|
|
1411
|
+
dtypes=dtypes,
|
|
1412
|
+
),
|
|
1413
|
+
parent_allocator=self,
|
|
1414
|
+
)
|
|
1415
|
+
)
|
|
1416
|
+
|
|
1417
|
+
return tensor_mem_objs
|
|
1418
|
+
|
|
1419
|
+
@_lmcache_nvtx_annotate
|
|
1420
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
1421
|
+
if not memory_obj.is_valid():
|
|
1422
|
+
return
|
|
1423
|
+
|
|
1424
|
+
self.address_manager.free(memory_obj.meta.address, memory_obj.meta.phy_size)
|
|
1425
|
+
memory_obj.invalidate()
|
|
1426
|
+
|
|
1427
|
+
# For debug
|
|
1428
|
+
self.num_active_allocations -= 1
|
|
1429
|
+
|
|
1430
|
+
# Update stats
|
|
1431
|
+
self.stats_monitor.update_local_cache_usage(
|
|
1432
|
+
self.address_manager.total_allocated_size
|
|
1433
|
+
)
|
|
1434
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1435
|
+
|
|
1436
|
+
@_lmcache_nvtx_annotate
|
|
1437
|
+
def batched_free(
|
|
1438
|
+
self,
|
|
1439
|
+
memory_objs: List[MemoryObj],
|
|
1440
|
+
allocator_type: Optional[str] = None,
|
|
1441
|
+
update_stats: bool = True,
|
|
1442
|
+
):
|
|
1443
|
+
"""
|
|
1444
|
+
Batched free memory objs.
|
|
1445
|
+
Unlike `batched_allocate`, this function does not
|
|
1446
|
+
assume that the memory objs are equal-sized.
|
|
1447
|
+
"""
|
|
1448
|
+
if not memory_objs:
|
|
1449
|
+
return
|
|
1450
|
+
|
|
1451
|
+
# Coalesce adjacent memory objects before freeing to reduce
|
|
1452
|
+
# the number of free operations
|
|
1453
|
+
coalesced_blocks: list[tuple[int, int, int]] = [] # (address, size, count)
|
|
1454
|
+
curr_start = None
|
|
1455
|
+
curr_size = 0
|
|
1456
|
+
curr_count = 0
|
|
1457
|
+
|
|
1458
|
+
memory_objs.sort(key=lambda x: x.meta.address)
|
|
1459
|
+
for memory_obj in memory_objs:
|
|
1460
|
+
if not memory_obj.is_valid():
|
|
1461
|
+
logger.warning("Trying to free an invalidated MemoryObj")
|
|
1462
|
+
continue
|
|
1463
|
+
memory_obj.invalidate()
|
|
1464
|
+
|
|
1465
|
+
if curr_start is None:
|
|
1466
|
+
curr_start = memory_obj.meta.address
|
|
1467
|
+
curr_size = memory_obj.meta.phy_size
|
|
1468
|
+
curr_count = 1
|
|
1469
|
+
elif curr_start + curr_size == memory_obj.meta.address:
|
|
1470
|
+
# Adjacent block, extend current
|
|
1471
|
+
curr_size += memory_obj.meta.phy_size
|
|
1472
|
+
curr_count += 1
|
|
1473
|
+
else:
|
|
1474
|
+
# Non-adjacent, save current and start new
|
|
1475
|
+
coalesced_blocks.append((curr_start, curr_size, curr_count))
|
|
1476
|
+
curr_start = memory_obj.meta.address
|
|
1477
|
+
curr_size = memory_obj.meta.phy_size
|
|
1478
|
+
curr_count = 1
|
|
1479
|
+
|
|
1480
|
+
if curr_start is not None:
|
|
1481
|
+
coalesced_blocks.append((curr_start, curr_size, curr_count))
|
|
1482
|
+
|
|
1483
|
+
# Free all coalesced blocks
|
|
1484
|
+
total_count = 0
|
|
1485
|
+
for address, size, count in coalesced_blocks:
|
|
1486
|
+
self.address_manager.free(address, size)
|
|
1487
|
+
total_count += count
|
|
1488
|
+
|
|
1489
|
+
# For debug
|
|
1490
|
+
self.num_active_allocations -= total_count
|
|
1491
|
+
|
|
1492
|
+
if update_stats:
|
|
1493
|
+
self.stats_monitor.update_local_cache_usage(
|
|
1494
|
+
self.address_manager.total_allocated_size
|
|
1495
|
+
)
|
|
1496
|
+
self.stats_monitor.update_active_memory_objs_count(
|
|
1497
|
+
self.num_active_allocations
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
def memcheck(self):
|
|
1501
|
+
"""For debug purposes.
|
|
1502
|
+
Returns True is everything is fine, otherwise False.
|
|
1503
|
+
"""
|
|
1504
|
+
clear = True
|
|
1505
|
+
logger.info("Checking memory allocator consistency")
|
|
1506
|
+
logger.info(f" - Total active allocations: {self.num_active_allocations}")
|
|
1507
|
+
logger.info(
|
|
1508
|
+
f" - Total allocated size: "
|
|
1509
|
+
f"{self.address_manager.total_allocated_size / 1048576} MB"
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
# Check the real total free size
|
|
1513
|
+
total_free_size = self.address_manager.get_free_size()
|
|
1514
|
+
logger.info(f" - Total free size: {total_free_size / 1048576} MB")
|
|
1515
|
+
|
|
1516
|
+
# Check if the numbers are consistent
|
|
1517
|
+
if (
|
|
1518
|
+
total_free_size + self.address_manager.total_allocated_size
|
|
1519
|
+
!= self.address_manager.get_heap_size()
|
|
1520
|
+
):
|
|
1521
|
+
logger.error("Memory allocator size is inconsistent")
|
|
1522
|
+
logger.error("This implies a bug in the memory allocator")
|
|
1523
|
+
clear = False
|
|
1524
|
+
|
|
1525
|
+
# Check if the blocks are coalesced
|
|
1526
|
+
if not self.address_manager.check_consistency():
|
|
1527
|
+
logger.error("Memory allocator has non-coalesced blocks")
|
|
1528
|
+
logger.error("This implies a bug in the memory allocator")
|
|
1529
|
+
clear = False
|
|
1530
|
+
|
|
1531
|
+
return clear
|
|
1532
|
+
|
|
1533
|
+
def __str__(self):
|
|
1534
|
+
return "TensorMemoryAllocator"
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
class PagedAddressManager:
|
|
1538
|
+
"""
|
|
1539
|
+
A lightweight address manager for PagedTensorMemoryAllocator.
|
|
1540
|
+
Provides get_free_size() and get_heap_size() by reading the
|
|
1541
|
+
paged allocator's state.
|
|
1542
|
+
"""
|
|
1543
|
+
|
|
1544
|
+
def __init__(self, paged_allocator: "PagedTensorMemoryAllocator"):
|
|
1545
|
+
self._allocator = paged_allocator
|
|
1546
|
+
|
|
1547
|
+
def get_heap_size(self) -> int:
|
|
1548
|
+
"""Get the total size of the paged address space in bytes."""
|
|
1549
|
+
return self._allocator.buffer_size
|
|
1550
|
+
|
|
1551
|
+
def get_free_size(self) -> int:
|
|
1552
|
+
"""Get the total free size in bytes."""
|
|
1553
|
+
return len(self._allocator.free_blocks) * self._allocator.align_bytes
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
class PagedTensorMemoryAllocator(MemoryAllocatorInterface):
|
|
1557
|
+
"""
|
|
1558
|
+
Implements a paged memory allocator.
|
|
1559
|
+
"""
|
|
1560
|
+
|
|
1561
|
+
def __init__(
|
|
1562
|
+
self,
|
|
1563
|
+
tensor: torch.Tensor,
|
|
1564
|
+
shapes: list[torch.Size],
|
|
1565
|
+
dtypes: list[torch.dtype],
|
|
1566
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1567
|
+
):
|
|
1568
|
+
self.buffer = tensor.view(torch.uint8).flatten()
|
|
1569
|
+
self.buffer_size = self.buffer.numel() * self.buffer.element_size()
|
|
1570
|
+
self.buffer_ptr = self.buffer.data_ptr()
|
|
1571
|
+
|
|
1572
|
+
self.shapes = shapes
|
|
1573
|
+
self.dtypes = dtypes
|
|
1574
|
+
self.fmt = fmt
|
|
1575
|
+
|
|
1576
|
+
# full chunk size bytes
|
|
1577
|
+
self.align_bytes = get_size_bytes(shapes, dtypes)
|
|
1578
|
+
|
|
1579
|
+
assert self.buffer_size % self.align_bytes == 0, (
|
|
1580
|
+
f"Buffer size {self.buffer_size} must be a"
|
|
1581
|
+
f" multiple of align bytes {self.align_bytes}"
|
|
1582
|
+
" in paged memory allocator."
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
self.paged_buffers = torch.split(self.buffer, self.align_bytes, dim=0)
|
|
1586
|
+
|
|
1587
|
+
# NOTE: deque is used since thread-safety is not a concern here as
|
|
1588
|
+
# is implemented in C under the hood (in CPython), and operations
|
|
1589
|
+
# on deque are atomic.
|
|
1590
|
+
self.free_blocks: deque[TensorMemoryObj] = deque()
|
|
1591
|
+
|
|
1592
|
+
for idx, buf in enumerate(self.paged_buffers):
|
|
1593
|
+
# NOTE: idx is the paged index
|
|
1594
|
+
# NOTE: the last unfull chunk's shape needs to be
|
|
1595
|
+
# adjusted during allocation.
|
|
1596
|
+
metadata = MemoryObjMetadata(
|
|
1597
|
+
self.shapes[0],
|
|
1598
|
+
self.dtypes[0],
|
|
1599
|
+
idx,
|
|
1600
|
+
self.align_bytes, # 1 page
|
|
1601
|
+
1, # ref_count=1
|
|
1602
|
+
0, # pin_count=0
|
|
1603
|
+
self.fmt,
|
|
1604
|
+
shapes=self.shapes,
|
|
1605
|
+
dtypes=self.dtypes,
|
|
1606
|
+
)
|
|
1607
|
+
mem_obj = TensorMemoryObj(
|
|
1608
|
+
raw_data=buf,
|
|
1609
|
+
metadata=metadata,
|
|
1610
|
+
parent_allocator=self,
|
|
1611
|
+
)
|
|
1612
|
+
self.free_blocks.append(mem_obj)
|
|
1613
|
+
|
|
1614
|
+
# Address manager for memory usage tracking
|
|
1615
|
+
self.address_manager = PagedAddressManager(self)
|
|
1616
|
+
|
|
1617
|
+
# For debugging purposes
|
|
1618
|
+
self.num_active_allocations = 0
|
|
1619
|
+
self.total_allocated_size = 0
|
|
1620
|
+
|
|
1621
|
+
self.stats_monitor = LMCStatsMonitor.GetOrCreate()
|
|
1622
|
+
logger.info(
|
|
1623
|
+
"Paged tensor memory allocator initialized, "
|
|
1624
|
+
"shapes: %s, dtypes: %s, align bytes: %s",
|
|
1625
|
+
self.shapes,
|
|
1626
|
+
self.dtypes,
|
|
1627
|
+
self.align_bytes,
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
@_lmcache_nvtx_annotate
|
|
1631
|
+
def allocate(
|
|
1632
|
+
self,
|
|
1633
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1634
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1635
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1636
|
+
allocator_type: Optional[str] = None,
|
|
1637
|
+
) -> Optional[TensorMemoryObj]:
|
|
1638
|
+
shapes, dtypes = self._adapt_shapes_and_dtypes(shapes, dtypes)
|
|
1639
|
+
|
|
1640
|
+
try:
|
|
1641
|
+
free_block = self.free_blocks.popleft()
|
|
1642
|
+
except IndexError:
|
|
1643
|
+
logger.debug(
|
|
1644
|
+
f"Failed to allocate memory for "
|
|
1645
|
+
f"tensor({shapes}, {dtypes}) because "
|
|
1646
|
+
"no free blocks is available"
|
|
1647
|
+
)
|
|
1648
|
+
return None
|
|
1649
|
+
|
|
1650
|
+
# TODO (Jiayi): This is a bit redundant.
|
|
1651
|
+
free_block.meta.shape = shapes[0]
|
|
1652
|
+
free_block.meta.dtype = dtypes[0]
|
|
1653
|
+
free_block.meta.shapes = shapes
|
|
1654
|
+
free_block.meta.dtypes = dtypes
|
|
1655
|
+
free_block.meta.fmt = fmt
|
|
1656
|
+
free_block.meta.ref_count = 1
|
|
1657
|
+
|
|
1658
|
+
if shapes != self.shapes:
|
|
1659
|
+
size_in_bytes = get_size_bytes(shapes, dtypes)
|
|
1660
|
+
free_block.raw_data = free_block.raw_data[:size_in_bytes]
|
|
1661
|
+
|
|
1662
|
+
# TODO (Jiayi): need a flag to drop these debug ops
|
|
1663
|
+
# NOTE (Jiayi): the following code is not thread-safe but
|
|
1664
|
+
# is tolerable as this is only used for debugging purposes.
|
|
1665
|
+
# Update debug status
|
|
1666
|
+
self.num_active_allocations += 1
|
|
1667
|
+
self.total_allocated_size += self.align_bytes
|
|
1668
|
+
self.stats_monitor.update_local_cache_usage(self.total_allocated_size)
|
|
1669
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1670
|
+
|
|
1671
|
+
# Allocate the block
|
|
1672
|
+
return free_block
|
|
1673
|
+
|
|
1674
|
+
@_lmcache_nvtx_annotate
|
|
1675
|
+
def batched_allocate(
|
|
1676
|
+
self,
|
|
1677
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1678
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1679
|
+
batch_size: int,
|
|
1680
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1681
|
+
allocator_type: Optional[str] = None,
|
|
1682
|
+
) -> Optional[List[TensorMemoryObj]]:
|
|
1683
|
+
"""
|
|
1684
|
+
Batched allocate tensor memory objs with pre-defined equal sizes.
|
|
1685
|
+
"""
|
|
1686
|
+
shapes, dtypes = self._adapt_shapes_and_dtypes(shapes, dtypes)
|
|
1687
|
+
|
|
1688
|
+
allocated_blocks: list[TensorMemoryObj] = []
|
|
1689
|
+
for i in range(batch_size):
|
|
1690
|
+
try:
|
|
1691
|
+
free_block = self.free_blocks.popleft()
|
|
1692
|
+
except IndexError:
|
|
1693
|
+
logger.debug(
|
|
1694
|
+
f"Failed to allocate memory for "
|
|
1695
|
+
f"tensor({shapes}, {dtypes}) because "
|
|
1696
|
+
"no free blocks is available"
|
|
1697
|
+
)
|
|
1698
|
+
self.batched_free(allocated_blocks, update_stats=False)
|
|
1699
|
+
return None
|
|
1700
|
+
|
|
1701
|
+
# FIXME: think about whether pareant_allocator
|
|
1702
|
+
# should be updated here.
|
|
1703
|
+
free_block.meta.shape = shapes[0]
|
|
1704
|
+
free_block.meta.dtype = dtypes[0]
|
|
1705
|
+
free_block.meta.shapes = shapes
|
|
1706
|
+
free_block.meta.dtypes = dtypes
|
|
1707
|
+
free_block.meta.fmt = fmt
|
|
1708
|
+
free_block.meta.ref_count = 1
|
|
1709
|
+
|
|
1710
|
+
if shapes != self.shapes:
|
|
1711
|
+
size_in_bytes = get_size_bytes(shapes, dtypes)
|
|
1712
|
+
free_block.raw_data = free_block.raw_data[:size_in_bytes]
|
|
1713
|
+
|
|
1714
|
+
allocated_blocks.append(free_block)
|
|
1715
|
+
|
|
1716
|
+
# TODO (Jiayi): need a flag to drop these debug ops
|
|
1717
|
+
# NOTE (Jiayi): the following code is not thread-safe but
|
|
1718
|
+
# is tolerable as this is only used for debugging purposes.
|
|
1719
|
+
# Update debug status
|
|
1720
|
+
self.num_active_allocations += batch_size
|
|
1721
|
+
self.total_allocated_size = self.num_active_allocations * self.align_bytes
|
|
1722
|
+
self.stats_monitor.update_local_cache_usage(self.total_allocated_size)
|
|
1723
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1724
|
+
|
|
1725
|
+
# Allocate the block
|
|
1726
|
+
return allocated_blocks
|
|
1727
|
+
|
|
1728
|
+
@_lmcache_nvtx_annotate
|
|
1729
|
+
def free(self, memory_obj: TensorMemoryObj, allocator_type: Optional[str] = None):
|
|
1730
|
+
if not memory_obj.is_valid():
|
|
1731
|
+
return
|
|
1732
|
+
if memory_obj.meta.shapes != self.shapes:
|
|
1733
|
+
page_idx = memory_obj.meta.address
|
|
1734
|
+
memory_obj.raw_data = self.paged_buffers[page_idx]
|
|
1735
|
+
|
|
1736
|
+
self.free_blocks.append(memory_obj)
|
|
1737
|
+
|
|
1738
|
+
# memory_obj.invalidate()
|
|
1739
|
+
|
|
1740
|
+
# TODO (Jiayi): need a flag to drop these debug ops
|
|
1741
|
+
# NOTE (Jiayi): the following code is not thread-safe but
|
|
1742
|
+
# is tolerable as this is only used for debugging purposes.
|
|
1743
|
+
# Update debug status
|
|
1744
|
+
self.total_allocated_size -= self.align_bytes
|
|
1745
|
+
self.num_active_allocations -= 1
|
|
1746
|
+
self.stats_monitor.update_local_cache_usage(self.total_allocated_size)
|
|
1747
|
+
self.stats_monitor.update_active_memory_objs_count(self.num_active_allocations)
|
|
1748
|
+
|
|
1749
|
+
@_lmcache_nvtx_annotate
|
|
1750
|
+
def batched_free(
|
|
1751
|
+
self,
|
|
1752
|
+
memory_objs: List[TensorMemoryObj],
|
|
1753
|
+
allocator_type: Optional[str] = None,
|
|
1754
|
+
update_stats: bool = True,
|
|
1755
|
+
):
|
|
1756
|
+
"""
|
|
1757
|
+
Batched free memory objs.
|
|
1758
|
+
Unlike `batched_allocate`, this function does not
|
|
1759
|
+
assume that the memory objs are equal-sized.
|
|
1760
|
+
"""
|
|
1761
|
+
if not memory_objs:
|
|
1762
|
+
return
|
|
1763
|
+
|
|
1764
|
+
for memory_obj in memory_objs:
|
|
1765
|
+
if not memory_obj.is_valid():
|
|
1766
|
+
logger.warning("Trying to free an invalidated MemoryObj")
|
|
1767
|
+
continue
|
|
1768
|
+
# memory_obj.invalidate()
|
|
1769
|
+
if memory_obj.meta.shapes != self.shapes:
|
|
1770
|
+
page_idx = memory_obj.meta.address
|
|
1771
|
+
memory_obj.raw_data = self.paged_buffers[page_idx]
|
|
1772
|
+
|
|
1773
|
+
self.free_blocks.append(memory_obj)
|
|
1774
|
+
|
|
1775
|
+
if update_stats:
|
|
1776
|
+
num_freed_blocks = len(memory_objs)
|
|
1777
|
+
# TODO (Jiayi): need a flag to drop these debug ops
|
|
1778
|
+
# NOTE (Jiayi): the following code is not thread-safe but
|
|
1779
|
+
# is tolerable as this is only used for debugging purposes.
|
|
1780
|
+
# Update debug status
|
|
1781
|
+
self.total_allocated_size -= self.align_bytes * num_freed_blocks
|
|
1782
|
+
self.num_active_allocations -= num_freed_blocks
|
|
1783
|
+
self.stats_monitor.update_local_cache_usage(self.total_allocated_size)
|
|
1784
|
+
self.stats_monitor.update_active_memory_objs_count(
|
|
1785
|
+
self.num_active_allocations
|
|
1786
|
+
)
|
|
1787
|
+
|
|
1788
|
+
def memcheck(self):
|
|
1789
|
+
"""For debug purposes.
|
|
1790
|
+
Returns True is everything is fine, otherwise False.
|
|
1791
|
+
"""
|
|
1792
|
+
|
|
1793
|
+
logger.info("Checking memory allocator consistency")
|
|
1794
|
+
logger.info(f" - Total active allocations: {self.num_active_allocations}")
|
|
1795
|
+
logger.info(
|
|
1796
|
+
f" - Total allocated size: {self.total_allocated_size / 1048576} MB"
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
# Check the real total free size
|
|
1800
|
+
total_free_size = len(self.free_blocks) * self.align_bytes
|
|
1801
|
+
logger.info(f" - Total free size: {total_free_size / 1048576} MB")
|
|
1802
|
+
|
|
1803
|
+
# Check if the numbers are consistent
|
|
1804
|
+
if total_free_size + self.total_allocated_size != self.buffer.numel():
|
|
1805
|
+
logger.error("Memory allocator size is inconsistent")
|
|
1806
|
+
logger.error("This implies a bug in the memory allocator")
|
|
1807
|
+
return False
|
|
1808
|
+
|
|
1809
|
+
return True
|
|
1810
|
+
|
|
1811
|
+
def __str__(self):
|
|
1812
|
+
return "PagedTensorMemoryAllocator"
|
|
1813
|
+
|
|
1814
|
+
def __del__(self):
|
|
1815
|
+
# FIXME: NIXL-related memory leak should be handled somewhere (else).
|
|
1816
|
+
del self.buffer
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
class BufferAllocator(MemoryAllocatorInterface):
|
|
1820
|
+
"""Allocates memory in the pre-allocated pinned memory."""
|
|
1821
|
+
|
|
1822
|
+
def __init__(self, device="cpu"):
|
|
1823
|
+
"""
|
|
1824
|
+
:param str device: The device of the buffer memory.
|
|
1825
|
+
"""
|
|
1826
|
+
self.device = device
|
|
1827
|
+
|
|
1828
|
+
@_lmcache_nvtx_annotate
|
|
1829
|
+
def allocate(
|
|
1830
|
+
self,
|
|
1831
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1832
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1833
|
+
fmt: MemoryFormat = MemoryFormat.BINARY_BUFFER,
|
|
1834
|
+
allocator_type: Optional[str] = None,
|
|
1835
|
+
) -> BytesBufferMemoryObj:
|
|
1836
|
+
if isinstance(shapes, list):
|
|
1837
|
+
n = shapes[0][0]
|
|
1838
|
+
else:
|
|
1839
|
+
n = shapes[0]
|
|
1840
|
+
byte_array = bytearray(n)
|
|
1841
|
+
return BytesBufferMemoryObj(byte_array)
|
|
1842
|
+
|
|
1843
|
+
@_lmcache_nvtx_annotate
|
|
1844
|
+
def batched_allocate(
|
|
1845
|
+
self,
|
|
1846
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1847
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1848
|
+
batch_size: int,
|
|
1849
|
+
fmt: MemoryFormat = MemoryFormat.BINARY_BUFFER,
|
|
1850
|
+
allocator_type: Optional[str] = None,
|
|
1851
|
+
) -> List[BytesBufferMemoryObj]:
|
|
1852
|
+
if isinstance(shapes, list):
|
|
1853
|
+
n = shapes[0][0]
|
|
1854
|
+
else:
|
|
1855
|
+
n = shapes[0]
|
|
1856
|
+
# TODO(Jiayi): Optimize the following loop.
|
|
1857
|
+
byte_arrays = [bytearray(n) for _ in range(batch_size)]
|
|
1858
|
+
return [BytesBufferMemoryObj(byte_array) for byte_array in byte_arrays]
|
|
1859
|
+
|
|
1860
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
1861
|
+
return
|
|
1862
|
+
|
|
1863
|
+
def batched_free(
|
|
1864
|
+
self,
|
|
1865
|
+
memory_objs: List[MemoryObj],
|
|
1866
|
+
allocator_type: Optional[str] = None,
|
|
1867
|
+
update_stats: bool = True,
|
|
1868
|
+
):
|
|
1869
|
+
return
|
|
1870
|
+
|
|
1871
|
+
def __str__(self):
|
|
1872
|
+
return "BufferAllocator"
|
|
1873
|
+
|
|
1874
|
+
def memcheck(self):
|
|
1875
|
+
return True
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
class HostMemoryAllocator(MemoryAllocatorInterface):
|
|
1879
|
+
"""Allocates memory in the pre-allocated Host memory."""
|
|
1880
|
+
|
|
1881
|
+
def __init__(self, size: int, use_paging: bool = False, **kwargs):
|
|
1882
|
+
"""
|
|
1883
|
+
:param int size: The size of the pinned memory in bytes.
|
|
1884
|
+
"""
|
|
1885
|
+
buffer = torch.empty(size, dtype=torch.uint8, device="cpu")
|
|
1886
|
+
|
|
1887
|
+
self.allocator: MemoryAllocatorInterface
|
|
1888
|
+
if use_paging:
|
|
1889
|
+
assert "shapes" in kwargs, (
|
|
1890
|
+
"shapes must be specified for paged memory allocator"
|
|
1891
|
+
)
|
|
1892
|
+
assert "dtypes" in kwargs, (
|
|
1893
|
+
"dtypes must be specified for paged memory allocator"
|
|
1894
|
+
)
|
|
1895
|
+
assert "fmt" in kwargs, "fmt must be specified for paged memory allocator"
|
|
1896
|
+
self.allocator = PagedTensorMemoryAllocator(
|
|
1897
|
+
tensor=buffer,
|
|
1898
|
+
shapes=kwargs["shapes"],
|
|
1899
|
+
dtypes=kwargs["dtypes"],
|
|
1900
|
+
fmt=kwargs["fmt"],
|
|
1901
|
+
)
|
|
1902
|
+
else:
|
|
1903
|
+
self.allocator = TensorMemoryAllocator(buffer)
|
|
1904
|
+
|
|
1905
|
+
self.host_mem_lock = threading.Lock() if not use_paging else nullcontext()
|
|
1906
|
+
|
|
1907
|
+
@_lmcache_nvtx_annotate
|
|
1908
|
+
def allocate(
|
|
1909
|
+
self,
|
|
1910
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1911
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1912
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1913
|
+
allocator_type: Optional[str] = None,
|
|
1914
|
+
) -> Optional[MemoryObj]:
|
|
1915
|
+
with self.host_mem_lock:
|
|
1916
|
+
return self.allocator.allocate(shapes, dtypes, fmt, str(self))
|
|
1917
|
+
|
|
1918
|
+
@_lmcache_nvtx_annotate
|
|
1919
|
+
def batched_allocate(
|
|
1920
|
+
self,
|
|
1921
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1922
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1923
|
+
batch_size: int,
|
|
1924
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1925
|
+
allocator_type: Optional[str] = None,
|
|
1926
|
+
) -> Optional[List[MemoryObj]]:
|
|
1927
|
+
with self.host_mem_lock:
|
|
1928
|
+
return self.allocator.batched_allocate(
|
|
1929
|
+
shapes, dtypes, batch_size, fmt, str(self)
|
|
1930
|
+
)
|
|
1931
|
+
|
|
1932
|
+
@_lmcache_nvtx_annotate
|
|
1933
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
1934
|
+
with self.host_mem_lock:
|
|
1935
|
+
self.allocator.free(memory_obj)
|
|
1936
|
+
|
|
1937
|
+
@_lmcache_nvtx_annotate
|
|
1938
|
+
def batched_free(
|
|
1939
|
+
self,
|
|
1940
|
+
memory_objs: List[MemoryObj],
|
|
1941
|
+
allocator_type: Optional[str] = None,
|
|
1942
|
+
update_stats: bool = True,
|
|
1943
|
+
):
|
|
1944
|
+
with self.host_mem_lock:
|
|
1945
|
+
self.allocator.batched_free(memory_objs)
|
|
1946
|
+
|
|
1947
|
+
def memcheck(self):
|
|
1948
|
+
with self.host_mem_lock:
|
|
1949
|
+
return self.allocator.memcheck()
|
|
1950
|
+
|
|
1951
|
+
def __str__(self):
|
|
1952
|
+
return "HostMemoryAllocator"
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
class PinMemoryAllocator(MemoryAllocatorInterface):
|
|
1956
|
+
"""Allocates memory in the pre-allocated pinned memory."""
|
|
1957
|
+
|
|
1958
|
+
def __init__(self, size: int, use_paging: bool = False, **kwargs):
|
|
1959
|
+
"""
|
|
1960
|
+
:param int size: The size of the pinned memory in bytes.
|
|
1961
|
+
"""
|
|
1962
|
+
self.size = size
|
|
1963
|
+
self.buffer = _allocate_cpu_memory(size)
|
|
1964
|
+
self._unregistered = False
|
|
1965
|
+
|
|
1966
|
+
self.allocator: MemoryAllocatorInterface
|
|
1967
|
+
if use_paging:
|
|
1968
|
+
assert "shapes" in kwargs, (
|
|
1969
|
+
"shapes must be specified for paged memory allocator"
|
|
1970
|
+
)
|
|
1971
|
+
assert "dtypes" in kwargs, (
|
|
1972
|
+
"dtypes must be specified for paged memory allocator"
|
|
1973
|
+
)
|
|
1974
|
+
assert "fmt" in kwargs, "fmt must be specified for paged memory allocator"
|
|
1975
|
+
self.allocator = PagedTensorMemoryAllocator(
|
|
1976
|
+
tensor=self.buffer,
|
|
1977
|
+
shapes=kwargs["shapes"],
|
|
1978
|
+
dtypes=kwargs["dtypes"],
|
|
1979
|
+
fmt=kwargs["fmt"],
|
|
1980
|
+
)
|
|
1981
|
+
else:
|
|
1982
|
+
self.allocator = TensorMemoryAllocator(self.buffer)
|
|
1983
|
+
|
|
1984
|
+
self.host_mem_lock = threading.Lock() if not use_paging else nullcontext()
|
|
1985
|
+
|
|
1986
|
+
@_lmcache_nvtx_annotate
|
|
1987
|
+
def allocate(
|
|
1988
|
+
self,
|
|
1989
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
1990
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
1991
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
1992
|
+
allocator_type: Optional[str] = None,
|
|
1993
|
+
) -> Optional[MemoryObj]:
|
|
1994
|
+
with self.host_mem_lock:
|
|
1995
|
+
return self.allocator.allocate(shapes, dtypes, fmt, str(self))
|
|
1996
|
+
|
|
1997
|
+
@_lmcache_nvtx_annotate
|
|
1998
|
+
def batched_allocate(
|
|
1999
|
+
self,
|
|
2000
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2001
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2002
|
+
batch_size: int,
|
|
2003
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2004
|
+
allocator_type: Optional[str] = None,
|
|
2005
|
+
) -> Optional[List[MemoryObj]]:
|
|
2006
|
+
with self.host_mem_lock:
|
|
2007
|
+
return self.allocator.batched_allocate(
|
|
2008
|
+
shapes, dtypes, batch_size, fmt, str(self)
|
|
2009
|
+
)
|
|
2010
|
+
|
|
2011
|
+
@_lmcache_nvtx_annotate
|
|
2012
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
2013
|
+
with self.host_mem_lock:
|
|
2014
|
+
self.allocator.free(memory_obj)
|
|
2015
|
+
|
|
2016
|
+
@_lmcache_nvtx_annotate
|
|
2017
|
+
def batched_free(
|
|
2018
|
+
self,
|
|
2019
|
+
memory_objs: List[MemoryObj],
|
|
2020
|
+
allocator_type: Optional[str] = None,
|
|
2021
|
+
update_stats: bool = True,
|
|
2022
|
+
):
|
|
2023
|
+
with self.host_mem_lock:
|
|
2024
|
+
self.allocator.batched_free(memory_objs)
|
|
2025
|
+
|
|
2026
|
+
def memcheck(self):
|
|
2027
|
+
with self.host_mem_lock:
|
|
2028
|
+
return self.allocator.memcheck()
|
|
2029
|
+
|
|
2030
|
+
def close(self):
|
|
2031
|
+
if not self._unregistered:
|
|
2032
|
+
if self.buffer.numel() == 0:
|
|
2033
|
+
return
|
|
2034
|
+
_free_cpu_memory(self.buffer, self.size)
|
|
2035
|
+
self._unregistered = True
|
|
2036
|
+
|
|
2037
|
+
def __str__(self):
|
|
2038
|
+
return "PinMemoryAllocator"
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
class MixedMemoryAllocator(MemoryAllocatorInterface):
|
|
2042
|
+
"""
|
|
2043
|
+
Allocates (1) memory in the pre-allocated pinned memory.
|
|
2044
|
+
(2) byte_array buffer memory.
|
|
2045
|
+
"""
|
|
2046
|
+
|
|
2047
|
+
def __init__(self, size: int, use_paging: bool = False, **kwargs):
|
|
2048
|
+
"""
|
|
2049
|
+
:param int size: The size of the pinned memory in bytes.
|
|
2050
|
+
"""
|
|
2051
|
+
|
|
2052
|
+
self.numa_mapping = kwargs.get("numa_mapping", None)
|
|
2053
|
+
self.align_bytes = kwargs.get("align_bytes", AddressManager.ALIGN_BYTES)
|
|
2054
|
+
if self.align_bytes <= 0 or self.align_bytes & (self.align_bytes - 1) != 0:
|
|
2055
|
+
raise ValueError("align_bytes must be a positive power of two")
|
|
2056
|
+
|
|
2057
|
+
# Extract shm_name from config.extra_config if available
|
|
2058
|
+
config = kwargs.get("config", None)
|
|
2059
|
+
if config is not None:
|
|
2060
|
+
self.shm_name: Optional[str] = config.get_extra_config_value(
|
|
2061
|
+
"shm_name", None
|
|
2062
|
+
)
|
|
2063
|
+
else:
|
|
2064
|
+
self.shm_name = kwargs.get("shm_name", None)
|
|
2065
|
+
|
|
2066
|
+
self.size = size
|
|
2067
|
+
|
|
2068
|
+
self.buffer = _allocate_cpu_memory(size, self.numa_mapping, self.shm_name)
|
|
2069
|
+
|
|
2070
|
+
self._unregistered = False
|
|
2071
|
+
|
|
2072
|
+
self.pin_allocator: MemoryAllocatorInterface
|
|
2073
|
+
if use_paging:
|
|
2074
|
+
assert "shapes" in kwargs, (
|
|
2075
|
+
"shapes must be specified for paged memory allocator"
|
|
2076
|
+
)
|
|
2077
|
+
assert "dtypes" in kwargs, (
|
|
2078
|
+
"dtypes must be specified for paged memory allocator"
|
|
2079
|
+
)
|
|
2080
|
+
assert "fmt" in kwargs, "fmt must be specified for paged memory allocator"
|
|
2081
|
+
self.pin_allocator = PagedTensorMemoryAllocator(
|
|
2082
|
+
tensor=self.buffer,
|
|
2083
|
+
shapes=kwargs["shapes"],
|
|
2084
|
+
dtypes=kwargs["dtypes"],
|
|
2085
|
+
fmt=kwargs["fmt"],
|
|
2086
|
+
)
|
|
2087
|
+
else:
|
|
2088
|
+
self.pin_allocator = TensorMemoryAllocator(
|
|
2089
|
+
self.buffer, align_bytes=self.align_bytes
|
|
2090
|
+
)
|
|
2091
|
+
|
|
2092
|
+
self.host_mem_lock = threading.Lock() if not use_paging else nullcontext()
|
|
2093
|
+
|
|
2094
|
+
self.buffer_allocator = BufferAllocator("cpu")
|
|
2095
|
+
|
|
2096
|
+
@_lmcache_nvtx_annotate
|
|
2097
|
+
def allocate(
|
|
2098
|
+
self,
|
|
2099
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2100
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2101
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2102
|
+
allocator_type: Optional[str] = None,
|
|
2103
|
+
) -> Optional[MemoryObj]:
|
|
2104
|
+
if fmt == MemoryFormat.BINARY_BUFFER:
|
|
2105
|
+
return self.buffer_allocator.allocate(shapes, dtypes, fmt)
|
|
2106
|
+
elif fmt in [
|
|
2107
|
+
MemoryFormat.KV_2LTD,
|
|
2108
|
+
MemoryFormat.KV_2TD,
|
|
2109
|
+
MemoryFormat.KV_T2D,
|
|
2110
|
+
MemoryFormat.KV_MLA_FMT,
|
|
2111
|
+
]:
|
|
2112
|
+
with self.host_mem_lock:
|
|
2113
|
+
return self.pin_allocator.allocate(shapes, dtypes, fmt, str(self))
|
|
2114
|
+
else:
|
|
2115
|
+
raise ValueError(f"Unsupported memory format: {fmt}")
|
|
2116
|
+
|
|
2117
|
+
@_lmcache_nvtx_annotate
|
|
2118
|
+
def batched_allocate(
|
|
2119
|
+
self,
|
|
2120
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2121
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2122
|
+
batch_size: int,
|
|
2123
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2124
|
+
allocator_type: Optional[str] = None,
|
|
2125
|
+
) -> Optional[List[MemoryObj]]:
|
|
2126
|
+
if fmt == MemoryFormat.BINARY_BUFFER:
|
|
2127
|
+
return self.buffer_allocator.batched_allocate(
|
|
2128
|
+
shapes, dtypes, batch_size, fmt
|
|
2129
|
+
)
|
|
2130
|
+
elif fmt in [
|
|
2131
|
+
MemoryFormat.KV_2LTD,
|
|
2132
|
+
MemoryFormat.KV_2TD,
|
|
2133
|
+
MemoryFormat.KV_T2D,
|
|
2134
|
+
MemoryFormat.KV_MLA_FMT,
|
|
2135
|
+
]:
|
|
2136
|
+
with self.host_mem_lock:
|
|
2137
|
+
return self.pin_allocator.batched_allocate(
|
|
2138
|
+
shapes, dtypes, batch_size, fmt, str(self)
|
|
2139
|
+
)
|
|
2140
|
+
else:
|
|
2141
|
+
raise ValueError(f"Unsupported memory format: {fmt}")
|
|
2142
|
+
|
|
2143
|
+
@_lmcache_nvtx_annotate
|
|
2144
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
2145
|
+
fmt = memory_obj.meta.fmt
|
|
2146
|
+
if fmt == MemoryFormat.BINARY_BUFFER:
|
|
2147
|
+
self.buffer_allocator.free(memory_obj)
|
|
2148
|
+
elif fmt in [
|
|
2149
|
+
MemoryFormat.KV_2LTD,
|
|
2150
|
+
MemoryFormat.KV_2TD,
|
|
2151
|
+
MemoryFormat.KV_T2D,
|
|
2152
|
+
MemoryFormat.KV_MLA_FMT,
|
|
2153
|
+
]:
|
|
2154
|
+
with self.host_mem_lock:
|
|
2155
|
+
self.pin_allocator.free(memory_obj)
|
|
2156
|
+
else:
|
|
2157
|
+
raise ValueError(f"Unsupported memory format: {fmt}")
|
|
2158
|
+
|
|
2159
|
+
@_lmcache_nvtx_annotate
|
|
2160
|
+
def batched_free(
|
|
2161
|
+
self,
|
|
2162
|
+
memory_objs: List[MemoryObj],
|
|
2163
|
+
allocator_type: Optional[str] = None,
|
|
2164
|
+
update_stats: bool = True,
|
|
2165
|
+
):
|
|
2166
|
+
if not memory_objs:
|
|
2167
|
+
return
|
|
2168
|
+
|
|
2169
|
+
# NOTE: fmts of all memory_objs should be the same
|
|
2170
|
+
fmt = memory_objs[0].meta.fmt
|
|
2171
|
+
if fmt == MemoryFormat.BINARY_BUFFER:
|
|
2172
|
+
self.buffer_allocator.batched_free(memory_objs)
|
|
2173
|
+
elif fmt in [
|
|
2174
|
+
MemoryFormat.KV_2LTD,
|
|
2175
|
+
MemoryFormat.KV_2TD,
|
|
2176
|
+
MemoryFormat.KV_T2D,
|
|
2177
|
+
MemoryFormat.KV_MLA_FMT,
|
|
2178
|
+
]:
|
|
2179
|
+
with self.host_mem_lock:
|
|
2180
|
+
self.pin_allocator.batched_free(memory_objs)
|
|
2181
|
+
else:
|
|
2182
|
+
raise ValueError(f"Unsupported memory format: {fmt}")
|
|
2183
|
+
|
|
2184
|
+
def memcheck(self):
|
|
2185
|
+
with self.host_mem_lock:
|
|
2186
|
+
return self.pin_allocator.memcheck()
|
|
2187
|
+
|
|
2188
|
+
def close(self):
|
|
2189
|
+
if not self._unregistered:
|
|
2190
|
+
if torch.cuda.is_available():
|
|
2191
|
+
torch.cuda.synchronize()
|
|
2192
|
+
if self.buffer.numel() == 0:
|
|
2193
|
+
return
|
|
2194
|
+
_free_cpu_memory(
|
|
2195
|
+
self.buffer,
|
|
2196
|
+
self.size,
|
|
2197
|
+
self.numa_mapping,
|
|
2198
|
+
self.shm_name,
|
|
2199
|
+
)
|
|
2200
|
+
self._unregistered = True
|
|
2201
|
+
|
|
2202
|
+
def __str__(self):
|
|
2203
|
+
return "MixedMemoryAllocator"
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
class GPUMemoryAllocator(MemoryAllocatorInterface):
|
|
2207
|
+
"""Allocates memory in the pre-allocated GPU memory."""
|
|
2208
|
+
|
|
2209
|
+
def __init__(
|
|
2210
|
+
self,
|
|
2211
|
+
size: int,
|
|
2212
|
+
device="cuda",
|
|
2213
|
+
align_bytes: Optional[int] = None,
|
|
2214
|
+
use_paging: bool = False,
|
|
2215
|
+
**kwargs,
|
|
2216
|
+
):
|
|
2217
|
+
"""
|
|
2218
|
+
:param int size: The size of the GPU memory in bytes.
|
|
2219
|
+
:param Optional[int] align_bytes: The byte alignment for allocations.
|
|
2220
|
+
"""
|
|
2221
|
+
if not torch.cuda.is_available():
|
|
2222
|
+
device = "cpu"
|
|
2223
|
+
|
|
2224
|
+
self.tensor = torch.empty(size, dtype=torch.uint8, device=device)
|
|
2225
|
+
|
|
2226
|
+
self.allocator: MemoryAllocatorInterface
|
|
2227
|
+
if use_paging:
|
|
2228
|
+
assert "shapes" in kwargs, (
|
|
2229
|
+
"shapes must be specified for paged memory allocator"
|
|
2230
|
+
)
|
|
2231
|
+
assert "dtypes" in kwargs, (
|
|
2232
|
+
"dtypes must be specified for paged memory allocator"
|
|
2233
|
+
)
|
|
2234
|
+
assert "fmt" in kwargs, "fmt must be specified for paged memory allocator"
|
|
2235
|
+
self.allocator = PagedTensorMemoryAllocator(
|
|
2236
|
+
tensor=self.tensor,
|
|
2237
|
+
shapes=kwargs["shapes"],
|
|
2238
|
+
dtypes=kwargs["dtypes"],
|
|
2239
|
+
fmt=kwargs["fmt"],
|
|
2240
|
+
)
|
|
2241
|
+
else:
|
|
2242
|
+
kwargs = {}
|
|
2243
|
+
if align_bytes is not None:
|
|
2244
|
+
kwargs["align_bytes"] = align_bytes
|
|
2245
|
+
self.allocator = TensorMemoryAllocator(self.tensor, **kwargs)
|
|
2246
|
+
|
|
2247
|
+
self.device_mem_lock = threading.Lock() if not use_paging else nullcontext()
|
|
2248
|
+
|
|
2249
|
+
@_lmcache_nvtx_annotate
|
|
2250
|
+
def allocate(
|
|
2251
|
+
self,
|
|
2252
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2253
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2254
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2255
|
+
allocator_type: Optional[str] = None,
|
|
2256
|
+
) -> Optional[MemoryObj]:
|
|
2257
|
+
with self.device_mem_lock:
|
|
2258
|
+
return self.allocator.allocate(shapes, dtypes, fmt, str(self))
|
|
2259
|
+
|
|
2260
|
+
@_lmcache_nvtx_annotate
|
|
2261
|
+
def batched_allocate(
|
|
2262
|
+
self,
|
|
2263
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2264
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2265
|
+
batch_size: int,
|
|
2266
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2267
|
+
allocator_type: Optional[str] = None,
|
|
2268
|
+
) -> Optional[List[MemoryObj]]:
|
|
2269
|
+
with self.device_mem_lock:
|
|
2270
|
+
return self.allocator.batched_allocate(
|
|
2271
|
+
shapes, dtypes, batch_size, fmt, str(self)
|
|
2272
|
+
)
|
|
2273
|
+
|
|
2274
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
2275
|
+
with self.device_mem_lock:
|
|
2276
|
+
self.allocator.free(memory_obj)
|
|
2277
|
+
|
|
2278
|
+
def batched_free(
|
|
2279
|
+
self,
|
|
2280
|
+
memory_objs: List[MemoryObj],
|
|
2281
|
+
allocator_type: Optional[str] = None,
|
|
2282
|
+
update_stats: bool = True,
|
|
2283
|
+
):
|
|
2284
|
+
with self.device_mem_lock:
|
|
2285
|
+
self.allocator.batched_free(memory_objs)
|
|
2286
|
+
|
|
2287
|
+
def memcheck(self):
|
|
2288
|
+
with self.device_mem_lock:
|
|
2289
|
+
return self.allocator.memcheck()
|
|
2290
|
+
|
|
2291
|
+
def __str__(self):
|
|
2292
|
+
return "GPUMemoryAllocator"
|
|
2293
|
+
|
|
2294
|
+
|
|
2295
|
+
class AdHocMemoryAllocator(MemoryAllocatorInterface):
|
|
2296
|
+
"""
|
|
2297
|
+
AdHocMemoryAllocator is a simple allocator that does not actually
|
|
2298
|
+
allocate memory. It is used for testing purposes only.
|
|
2299
|
+
"""
|
|
2300
|
+
|
|
2301
|
+
def __init__(self, device: str = "cpu"):
|
|
2302
|
+
"""
|
|
2303
|
+
:param str device: The device of the ad hoc memory allocator.
|
|
2304
|
+
"""
|
|
2305
|
+
if not torch.cuda.is_available():
|
|
2306
|
+
self.device = "cpu"
|
|
2307
|
+
else:
|
|
2308
|
+
self.device = device
|
|
2309
|
+
|
|
2310
|
+
@_lmcache_nvtx_annotate
|
|
2311
|
+
def allocate(
|
|
2312
|
+
self,
|
|
2313
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2314
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2315
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2316
|
+
allocator_type: Optional[str] = None,
|
|
2317
|
+
) -> Optional[MemoryObj]:
|
|
2318
|
+
"""
|
|
2319
|
+
Returns a dummy MemoryObj for testing purposes.
|
|
2320
|
+
"""
|
|
2321
|
+
shapes, dtypes = self._adapt_shapes_and_dtypes(shapes, dtypes)
|
|
2322
|
+
size = get_size_bytes(shapes, dtypes)
|
|
2323
|
+
|
|
2324
|
+
# Return a dummy object with no actual memory allocation
|
|
2325
|
+
return TensorMemoryObj(
|
|
2326
|
+
raw_data=torch.empty(
|
|
2327
|
+
torch.Size([size]), dtype=torch.uint8, device=self.device
|
|
2328
|
+
),
|
|
2329
|
+
metadata=MemoryObjMetadata(
|
|
2330
|
+
shape=shapes[0],
|
|
2331
|
+
dtype=dtypes[0],
|
|
2332
|
+
address=0,
|
|
2333
|
+
phy_size=0,
|
|
2334
|
+
ref_count=1,
|
|
2335
|
+
pin_count=0,
|
|
2336
|
+
fmt=fmt,
|
|
2337
|
+
shapes=shapes,
|
|
2338
|
+
dtypes=dtypes,
|
|
2339
|
+
),
|
|
2340
|
+
parent_allocator=self,
|
|
2341
|
+
)
|
|
2342
|
+
|
|
2343
|
+
@_lmcache_nvtx_annotate
|
|
2344
|
+
def batched_allocate(
|
|
2345
|
+
self,
|
|
2346
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2347
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2348
|
+
batch_size: int,
|
|
2349
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2350
|
+
allocator_type: Optional[str] = None,
|
|
2351
|
+
) -> Optional[List[MemoryObj]]:
|
|
2352
|
+
raise NotImplementedError(
|
|
2353
|
+
"Batched allocation is not supported in AdHocMemoryAllocator"
|
|
2354
|
+
)
|
|
2355
|
+
|
|
2356
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
2357
|
+
pass
|
|
2358
|
+
|
|
2359
|
+
def batched_free(
|
|
2360
|
+
self,
|
|
2361
|
+
memory_objs: List[MemoryObj],
|
|
2362
|
+
allocator_type: Optional[str] = None,
|
|
2363
|
+
update_stats: bool = True,
|
|
2364
|
+
):
|
|
2365
|
+
pass
|
|
2366
|
+
|
|
2367
|
+
def ref_count_up(self, memory_obj: MemoryObj):
|
|
2368
|
+
pass
|
|
2369
|
+
|
|
2370
|
+
def ref_count_down(self, memory_obj: MemoryObj):
|
|
2371
|
+
pass
|
|
2372
|
+
|
|
2373
|
+
def get_ref_count(self, memory_obj: MemoryObj):
|
|
2374
|
+
return 0
|
|
2375
|
+
|
|
2376
|
+
def memcheck(self):
|
|
2377
|
+
return True
|
|
2378
|
+
|
|
2379
|
+
def __str__(self):
|
|
2380
|
+
return "AdHocMemoryAllocator"
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
class CuFileMemoryAllocator(GPUMemoryAllocator):
|
|
2384
|
+
def __init__(self, size: int, device=None):
|
|
2385
|
+
# HACK(Jiayi): cufile import is buggy on some hardware
|
|
2386
|
+
# (e.g., without GPUDirect), so it's temporarily put here.
|
|
2387
|
+
# Third Party
|
|
2388
|
+
from cufile.bindings import cuFileBufDeregister, cuFileBufRegister
|
|
2389
|
+
|
|
2390
|
+
self.cuFileBufDeregister = cuFileBufDeregister
|
|
2391
|
+
if device is None:
|
|
2392
|
+
# TODO(Serapheim): Ideally we'd get the device from the upper
|
|
2393
|
+
# layer - for now just use the current device.
|
|
2394
|
+
if torch.cuda.is_available():
|
|
2395
|
+
device = f"cuda:{torch.cuda.current_device()}"
|
|
2396
|
+
else:
|
|
2397
|
+
device = "cpu:0"
|
|
2398
|
+
super().__init__(size, device, align_bytes=4096)
|
|
2399
|
+
self.base_pointer = self.tensor.data_ptr()
|
|
2400
|
+
cuFileBufRegister(ctypes.c_void_p(self.base_pointer), size, flags=0)
|
|
2401
|
+
|
|
2402
|
+
def __del__(self):
|
|
2403
|
+
self.cuFileBufDeregister(ctypes.c_void_p(self.base_pointer))
|
|
2404
|
+
|
|
2405
|
+
def __str__(self):
|
|
2406
|
+
return "CuFileMemoryAllocator"
|
|
2407
|
+
|
|
2408
|
+
|
|
2409
|
+
class HipFileMemoryAllocator(GPUMemoryAllocator):
|
|
2410
|
+
def __init__(self, size: int, device=None):
|
|
2411
|
+
# HACK: hipfile import is placed here to avoid import errors on
|
|
2412
|
+
# hardware without GPUDirect Storage / hipFile support.
|
|
2413
|
+
# Third Party
|
|
2414
|
+
from hipfile.bindings import hipFileBufDeregister, hipFileBufRegister
|
|
2415
|
+
|
|
2416
|
+
self.hipFileBufDeregister = hipFileBufDeregister
|
|
2417
|
+
if device is None:
|
|
2418
|
+
if torch.cuda.is_available():
|
|
2419
|
+
# TODO: On ROCm, PyTorch still uses the CUDA API internally
|
|
2420
|
+
device = f"cuda:{torch.cuda.current_device()}"
|
|
2421
|
+
else:
|
|
2422
|
+
device = "cpu:0"
|
|
2423
|
+
|
|
2424
|
+
super().__init__(size, device, align_bytes=4096)
|
|
2425
|
+
self.base_pointer = self.tensor.data_ptr()
|
|
2426
|
+
hipFileBufRegister(ctypes.c_void_p(self.base_pointer), size, flags=0)
|
|
2427
|
+
|
|
2428
|
+
def __del__(self):
|
|
2429
|
+
self.hipFileBufDeregister(ctypes.c_void_p(self.base_pointer))
|
|
2430
|
+
|
|
2431
|
+
def __str__(self):
|
|
2432
|
+
return "HipFileMemoryAllocator"
|
|
2433
|
+
|
|
2434
|
+
|
|
2435
|
+
class PagedCpuGpuMemoryAllocator(MemoryAllocatorInterface):
|
|
2436
|
+
"""
|
|
2437
|
+
Paged Memory Allocator for both CPU and GPU memory.
|
|
2438
|
+
This is a paged memory allocator for PD and P2P sharing
|
|
2439
|
+
when NIXL is enabled as NIXL relies on the paging abstraction.
|
|
2440
|
+
"""
|
|
2441
|
+
|
|
2442
|
+
def __init__(self):
|
|
2443
|
+
pass
|
|
2444
|
+
|
|
2445
|
+
def init_gpu_memory_allocator(
|
|
2446
|
+
self,
|
|
2447
|
+
size: int,
|
|
2448
|
+
shapes: list[torch.Size],
|
|
2449
|
+
dtypes: list[torch.dtype],
|
|
2450
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2451
|
+
device: str = "cuda",
|
|
2452
|
+
):
|
|
2453
|
+
self.gpu_buffer = torch.empty(
|
|
2454
|
+
size,
|
|
2455
|
+
dtype=torch.uint8,
|
|
2456
|
+
device=device,
|
|
2457
|
+
)
|
|
2458
|
+
self.gpu_allocator = PagedTensorMemoryAllocator(
|
|
2459
|
+
self.gpu_buffer,
|
|
2460
|
+
shapes,
|
|
2461
|
+
dtypes,
|
|
2462
|
+
fmt,
|
|
2463
|
+
)
|
|
2464
|
+
|
|
2465
|
+
def init_cpu_memory_allocator(
|
|
2466
|
+
self,
|
|
2467
|
+
size: int,
|
|
2468
|
+
shapes: list[torch.Size],
|
|
2469
|
+
dtypes: list[torch.dtype],
|
|
2470
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2471
|
+
numa_mapping: Optional[NUMAMapping] = None,
|
|
2472
|
+
):
|
|
2473
|
+
self.cpu_buffer = _allocate_cpu_memory(size, numa_mapping)
|
|
2474
|
+
self.cpu_allocator = PagedTensorMemoryAllocator(
|
|
2475
|
+
self.cpu_buffer,
|
|
2476
|
+
shapes,
|
|
2477
|
+
dtypes,
|
|
2478
|
+
fmt,
|
|
2479
|
+
)
|
|
2480
|
+
self.align_bytes = self.cpu_allocator.align_bytes
|
|
2481
|
+
|
|
2482
|
+
def allocate(
|
|
2483
|
+
self,
|
|
2484
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2485
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2486
|
+
fmt: MemoryFormat = MemoryFormat.UNDEFINED,
|
|
2487
|
+
allocator_type: Optional[str] = "cpu",
|
|
2488
|
+
) -> Optional[MemoryObj]:
|
|
2489
|
+
if allocator_type == "gpu":
|
|
2490
|
+
return self.gpu_allocator.allocate(shapes, dtypes, fmt)
|
|
2491
|
+
elif allocator_type == "cpu":
|
|
2492
|
+
return self.cpu_allocator.allocate(shapes, dtypes, fmt)
|
|
2493
|
+
else:
|
|
2494
|
+
raise ValueError(f"Unsupported allocator type: {allocator_type}")
|
|
2495
|
+
|
|
2496
|
+
def batched_allocate(
|
|
2497
|
+
self,
|
|
2498
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2499
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2500
|
+
batch_size: int,
|
|
2501
|
+
fmt: MemoryFormat = MemoryFormat.UNDEFINED,
|
|
2502
|
+
allocator_type: Optional[str] = "gpu",
|
|
2503
|
+
) -> Optional[List[MemoryObj]]:
|
|
2504
|
+
if allocator_type == "gpu":
|
|
2505
|
+
return self.gpu_allocator.batched_allocate(shapes, dtypes, batch_size, fmt)
|
|
2506
|
+
elif allocator_type == "cpu":
|
|
2507
|
+
return self.cpu_allocator.batched_allocate(shapes, dtypes, batch_size, fmt)
|
|
2508
|
+
else:
|
|
2509
|
+
raise ValueError(f"Unsupported allocator type: {allocator_type}")
|
|
2510
|
+
|
|
2511
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = "cpu"):
|
|
2512
|
+
if allocator_type == "gpu":
|
|
2513
|
+
self.gpu_allocator.free(memory_obj)
|
|
2514
|
+
elif allocator_type == "cpu":
|
|
2515
|
+
self.cpu_allocator.free(memory_obj)
|
|
2516
|
+
else:
|
|
2517
|
+
raise ValueError(f"Unsupported allocator type: {allocator_type}")
|
|
2518
|
+
|
|
2519
|
+
def batched_free(
|
|
2520
|
+
self,
|
|
2521
|
+
memory_objs: List[MemoryObj],
|
|
2522
|
+
allocator_type: Optional[str] = None,
|
|
2523
|
+
update_stats: bool = True,
|
|
2524
|
+
):
|
|
2525
|
+
if allocator_type == "gpu":
|
|
2526
|
+
self.gpu_allocator.batched_free(memory_objs, update_stats=update_stats)
|
|
2527
|
+
elif allocator_type == "cpu":
|
|
2528
|
+
self.cpu_allocator.batched_free(memory_objs, update_stats=update_stats)
|
|
2529
|
+
else:
|
|
2530
|
+
raise ValueError(f"Unsupported allocator type: {allocator_type}")
|
|
2531
|
+
|
|
2532
|
+
def __str__(self):
|
|
2533
|
+
return "PDMemoryAllocator"
|
|
2534
|
+
|
|
2535
|
+
|
|
2536
|
+
class XPUMemoryAllocator(MemoryAllocatorInterface):
|
|
2537
|
+
"""Allocates memory in the pre-allocated XPU memory."""
|
|
2538
|
+
|
|
2539
|
+
def __init__(
|
|
2540
|
+
self,
|
|
2541
|
+
size: int,
|
|
2542
|
+
device="xpu",
|
|
2543
|
+
align_bytes: Optional[int] = None,
|
|
2544
|
+
use_paging: bool = False,
|
|
2545
|
+
**kwargs,
|
|
2546
|
+
):
|
|
2547
|
+
self.tensor = torch.empty((size,), dtype=torch.uint8, device=device)
|
|
2548
|
+
|
|
2549
|
+
self.allocator: MemoryAllocatorInterface
|
|
2550
|
+
if use_paging:
|
|
2551
|
+
assert "shapes" in kwargs, (
|
|
2552
|
+
"shapes must be specified for paged memory allocator"
|
|
2553
|
+
)
|
|
2554
|
+
assert "dtypes" in kwargs, (
|
|
2555
|
+
"dtypes must be specified for paged memory allocator"
|
|
2556
|
+
)
|
|
2557
|
+
assert "fmt" in kwargs, "fmt must be specified for paged memory allocator"
|
|
2558
|
+
self.allocator = PagedTensorMemoryAllocator(
|
|
2559
|
+
tensor=self.tensor,
|
|
2560
|
+
shapes=kwargs["shapes"],
|
|
2561
|
+
dtypes=kwargs["dtypes"],
|
|
2562
|
+
fmt=kwargs["fmt"],
|
|
2563
|
+
)
|
|
2564
|
+
else:
|
|
2565
|
+
alloc_kwargs = {}
|
|
2566
|
+
if align_bytes is not None:
|
|
2567
|
+
alloc_kwargs["align_bytes"] = align_bytes
|
|
2568
|
+
self.allocator = TensorMemoryAllocator(self.tensor, **alloc_kwargs)
|
|
2569
|
+
|
|
2570
|
+
self.device_mem_lock = threading.Lock() if not use_paging else nullcontext()
|
|
2571
|
+
|
|
2572
|
+
@_lmcache_nvtx_annotate
|
|
2573
|
+
def allocate(
|
|
2574
|
+
self,
|
|
2575
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2576
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2577
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2578
|
+
allocator_type: Optional[str] = None,
|
|
2579
|
+
) -> Optional[MemoryObj]:
|
|
2580
|
+
with self.device_mem_lock:
|
|
2581
|
+
return self.allocator.allocate(shapes, dtypes, fmt, str(self))
|
|
2582
|
+
|
|
2583
|
+
@_lmcache_nvtx_annotate
|
|
2584
|
+
def batched_allocate(
|
|
2585
|
+
self,
|
|
2586
|
+
shapes: Union[torch.Size, list[torch.Size]],
|
|
2587
|
+
dtypes: Union[torch.dtype, list[torch.dtype]],
|
|
2588
|
+
batch_size: int,
|
|
2589
|
+
fmt: MemoryFormat = MemoryFormat.KV_2LTD,
|
|
2590
|
+
allocator_type: Optional[str] = None,
|
|
2591
|
+
) -> Optional[List[MemoryObj]]:
|
|
2592
|
+
with self.device_mem_lock:
|
|
2593
|
+
return self.allocator.batched_allocate(
|
|
2594
|
+
shapes, dtypes, batch_size, fmt, str(self)
|
|
2595
|
+
)
|
|
2596
|
+
|
|
2597
|
+
def free(self, memory_obj: MemoryObj, allocator_type: Optional[str] = None):
|
|
2598
|
+
with self.device_mem_lock:
|
|
2599
|
+
self.allocator.free(memory_obj)
|
|
2600
|
+
|
|
2601
|
+
def batched_free(
|
|
2602
|
+
self,
|
|
2603
|
+
memory_objs: List[MemoryObj],
|
|
2604
|
+
allocator_type: Optional[str] = None,
|
|
2605
|
+
update_stats: bool = True,
|
|
2606
|
+
):
|
|
2607
|
+
with self.device_mem_lock:
|
|
2608
|
+
self.allocator.batched_free(memory_objs)
|
|
2609
|
+
|
|
2610
|
+
def memcheck(self):
|
|
2611
|
+
with self.device_mem_lock:
|
|
2612
|
+
return self.allocator.memcheck()
|
|
2613
|
+
|
|
2614
|
+
def close(self):
|
|
2615
|
+
if hasattr(torch, "xpu") and torch.xpu.is_available():
|
|
2616
|
+
torch.xpu.synchronize()
|
|
2617
|
+
|
|
2618
|
+
def __str__(self):
|
|
2619
|
+
return "XPUMemoryAllocator"
|